Merge pull request #10200 from opf/fix/fine-tuning-of-ds

Fine tuning of DS basics
pull/10290/head
Oliver Günther 3 years ago committed by GitHub
commit 8f09b7cf3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      config/locales/js-en.yml
  2. 8
      config/routes.rb
  3. 3
      frontend/.eslintrc.js
  4. 3
      frontend/angular.json
  5. 310
      frontend/package-lock.json
  6. 2
      frontend/package.json
  7. 67
      frontend/src/app/shared/components/project-include/project-include.component.html
  8. 21
      frontend/src/app/shared/components/project-include/project-include.component.sass
  9. 13
      frontend/src/app/shared/components/project-include/project-list.component.html
  10. 2
      frontend/src/app/shared/components/project-include/project-list.component.ts
  11. 2
      frontend/src/app/shared/directives/search-highlight.directive.sass
  12. 43
      frontend/src/app/shared/directives/search-highlight.directive.ts
  13. 5
      frontend/src/app/shared/shared.module.ts
  14. 0
      frontend/src/app/spot/components/checkbox/checkbox.component.html
  15. 26
      frontend/src/app/spot/components/checkbox/checkbox.component.ts
  16. 0
      frontend/src/app/spot/components/chip-field/chip-field.component.html
  17. 28
      frontend/src/app/spot/components/chip-field/chip-field.component.ts
  18. 8
      frontend/src/app/spot/components/drop-modal.component.html
  19. 17
      frontend/src/app/spot/components/drop-modal/drop-modal.component.html
  20. 42
      frontend/src/app/spot/components/drop-modal/drop-modal.component.ts
  21. 7
      frontend/src/app/spot/components/filter-chip.component.html
  22. 16
      frontend/src/app/spot/components/filter-chip/filter-chip.component.html
  23. 17
      frontend/src/app/spot/components/filter-chip/filter-chip.component.ts
  24. 0
      frontend/src/app/spot/components/text-field/text-field.component.html
  25. 17
      frontend/src/app/spot/components/text-field/text-field.component.ts
  26. 0
      frontend/src/app/spot/components/toggle/toggle.component.html
  27. 14
      frontend/src/app/spot/components/toggle/toggle.component.ts
  28. 0
      frontend/src/app/spot/icon-font/LICENSE.md
  29. 10
      frontend/src/app/spot/icon-font/README.md
  30. 57
      frontend/src/app/spot/icon-font/generate.js
  31. 28
      frontend/src/app/spot/icon-font/icon.template.sass
  32. 0
      frontend/src/app/spot/icon-font/openproject-icon-font.template.lsg
  33. 9
      frontend/src/app/spot/icon-font/openproject-icon-font.template.sass
  34. 0
      frontend/src/app/spot/icon-font/src/accessibility.svg
  35. 0
      frontend/src/app/spot/icon-font/src/accountable.svg
  36. 0
      frontend/src/app/spot/icon-font/src/add.svg
  37. 0
      frontend/src/app/spot/icon-font/src/align-center.svg
  38. 0
      frontend/src/app/spot/icon-font/src/align-justify.svg
  39. 0
      frontend/src/app/spot/icon-font/src/align-left.svg
  40. 0
      frontend/src/app/spot/icon-font/src/align-right.svg
  41. 0
      frontend/src/app/spot/icon-font/src/arrow-down1.svg
  42. 0
      frontend/src/app/spot/icon-font/src/arrow-down2.svg
  43. 0
      frontend/src/app/spot/icon-font/src/arrow-left-right.svg
  44. 0
      frontend/src/app/spot/icon-font/src/arrow-left1.svg
  45. 0
      frontend/src/app/spot/icon-font/src/arrow-left2.svg
  46. 0
      frontend/src/app/spot/icon-font/src/arrow-left3.svg
  47. 0
      frontend/src/app/spot/icon-font/src/arrow-left4.svg
  48. 0
      frontend/src/app/spot/icon-font/src/arrow-right2.svg
  49. 0
      frontend/src/app/spot/icon-font/src/arrow-right3.svg
  50. 0
      frontend/src/app/spot/icon-font/src/arrow-right4.svg
  51. 0
      frontend/src/app/spot/icon-font/src/arrow-right5.svg
  52. 0
      frontend/src/app/spot/icon-font/src/arrow-right6.svg
  53. 0
      frontend/src/app/spot/icon-font/src/arrow-right7.svg
  54. 0
      frontend/src/app/spot/icon-font/src/arrow-thin.svg
  55. 0
      frontend/src/app/spot/icon-font/src/arrow-up1.svg
  56. 0
      frontend/src/app/spot/icon-font/src/arrow-up2.svg
  57. 0
      frontend/src/app/spot/icon-font/src/assigned-to-me.svg
  58. 0
      frontend/src/app/spot/icon-font/src/assigned.svg
  59. 0
      frontend/src/app/spot/icon-font/src/attachment.svg
  60. 0
      frontend/src/app/spot/icon-font/src/attention.svg
  61. 0
      frontend/src/app/spot/icon-font/src/back-up.svg
  62. 0
      frontend/src/app/spot/icon-font/src/backlogs.svg
  63. 0
      frontend/src/app/spot/icon-font/src/bcf.svg
  64. 0
      frontend/src/app/spot/icon-font/src/bell.svg
  65. 0
      frontend/src/app/spot/icon-font/src/billing-information.svg
  66. 0
      frontend/src/app/spot/icon-font/src/boards.svg
  67. 0
      frontend/src/app/spot/icon-font/src/bold.svg
  68. 0
      frontend/src/app/spot/icon-font/src/budget.svg
  69. 0
      frontend/src/app/spot/icon-font/src/bug.svg
  70. 0
      frontend/src/app/spot/icon-font/src/calendar.svg
  71. 0
      frontend/src/app/spot/icon-font/src/calendar2.svg
  72. 0
      frontend/src/app/spot/icon-font/src/camera.svg
  73. 0
      frontend/src/app/spot/icon-font/src/cancel.svg
  74. 0
      frontend/src/app/spot/icon-font/src/cart.svg
  75. 0
      frontend/src/app/spot/icon-font/src/changeset-down.svg
  76. 0
      frontend/src/app/spot/icon-font/src/changeset-up.svg
  77. 0
      frontend/src/app/spot/icon-font/src/changeset.svg
  78. 0
      frontend/src/app/spot/icon-font/src/chart1.svg
  79. 0
      frontend/src/app/spot/icon-font/src/chart2.svg
  80. 0
      frontend/src/app/spot/icon-font/src/chart3.svg
  81. 0
      frontend/src/app/spot/icon-font/src/checkmark.svg
  82. 0
      frontend/src/app/spot/icon-font/src/close.svg
  83. 0
      frontend/src/app/spot/icon-font/src/code-tag.svg
  84. 0
      frontend/src/app/spot/icon-font/src/color-text.svg
  85. 0
      frontend/src/app/spot/icon-font/src/color-underline.svg
  86. 0
      frontend/src/app/spot/icon-font/src/column-left.svg
  87. 0
      frontend/src/app/spot/icon-font/src/column-right.svg
  88. 0
      frontend/src/app/spot/icon-font/src/columns.svg
  89. 0
      frontend/src/app/spot/icon-font/src/compare2.svg
  90. 0
      frontend/src/app/spot/icon-font/src/concept.svg
  91. 0
      frontend/src/app/spot/icon-font/src/console-light.svg
  92. 0
      frontend/src/app/spot/icon-font/src/console.svg
  93. 0
      frontend/src/app/spot/icon-font/src/contacts.svg
  94. 0
      frontend/src/app/spot/icon-font/src/copy.svg
  95. 0
      frontend/src/app/spot/icon-font/src/cost-reports.svg
  96. 0
      frontend/src/app/spot/icon-font/src/cost-types.svg
  97. 0
      frontend/src/app/spot/icon-font/src/cursor.svg
  98. 0
      frontend/src/app/spot/icon-font/src/custom-development.svg
  99. 0
      frontend/src/app/spot/icon-font/src/custom-fields.svg
  100. 0
      frontend/src/app/spot/icon-font/src/cut.svg
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1266,3 +1266,9 @@ en:
load_error_message: 'There was an error loading the form' load_error_message: 'There was an error loading the form'
validation_error_message: 'Please fix the errors present in the form' validation_error_message: 'Please fix the errors present in the form'
advanced_settings: 'Advanced settings' advanced_settings: 'Advanced settings'
spot:
filter_chip:
remove: 'Remove'
drop_modal:
Close: 'Close'

@ -581,9 +581,7 @@ OpenProject::Application.routes.draw do
get '(/*state)', to: 'angular#notifications_layout', as: :notifications_center get '(/*state)', to: 'angular#notifications_layout', as: :notifications_center
end end
# Development route for styleguide # Routes for design related documentation and examples pages
if Rails.env.development? get '/design/spot', to: 'angular#empty_layout'
get '/spot-docs', to: 'angular#empty_layout' get '/design/styleguide' => redirect('/assets/styleguide.html')
get '/styleguide' => redirect('/assets/styleguide.html')
end
end end

@ -62,6 +62,9 @@ module.exports = {
}, },
], ],
// Sometimes we need to shush the TypeScript compiler
"no-unused-vars": ["error", { "varsIgnorePattern": "^_" }],
// Who cares about line length // Who cares about line length
"max-len": "off", "max-len": "off",

@ -52,7 +52,8 @@
], ],
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"includePaths": [ "includePaths": [
"src/assets/sass/" "src/assets/sass/",
"src/app/spot/styles/sass/variables/"
] ]
}, },
"scripts": [], "scripts": [],

@ -5606,7 +5606,6 @@
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"file-uri-to-path": "1.0.0" "file-uri-to-path": "1.0.0"
} }
@ -5638,6 +5637,15 @@
} }
} }
}, },
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"dev": true,
"requires": {
"inherits": "~2.0.0"
}
},
"bluebird": { "bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -5765,6 +5773,15 @@
"integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
"dev": true "dev": true
}, },
"bufferstreams": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.1.3.tgz",
"integrity": "sha512-HaJnVuslRF4g2kSDeyl++AaVizoitCpL9PglzCYwy0uHHyvWerfvEb8jWmYbF1z4kiVFolGomnxSGl+GUQp2jg==",
"dev": true,
"requires": {
"readable-stream": "^2.0.2"
}
},
"builtins": { "builtins": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
@ -7084,6 +7101,12 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz",
"integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==" "integrity": "sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA=="
}, },
"cubic2quad": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cubic2quad/-/cubic2quad-1.2.1.tgz",
"integrity": "sha512-wT5Y7mO8abrV16gnssKdmIhIbA9wSkeMzhh27jAguKrV82i24wER0vL5TGhUJ9dbJNDcigoRZ0IAHFEEEI4THQ==",
"dev": true
},
"custom-event": { "custom-event": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
@ -8947,8 +8970,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true, "dev": true
"optional": true
}, },
"filelist": { "filelist": {
"version": "1.0.2", "version": "1.0.2",
@ -9218,6 +9240,18 @@
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"optional": true "optional": true
}, },
"fstream": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
"dev": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
"mkdirp": ">=0.5 0",
"rimraf": "2"
}
},
"function-bind": { "function-bind": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
@ -12166,6 +12200,12 @@
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"dev": true "dev": true
}, },
"microbuffer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/microbuffer/-/microbuffer-1.0.0.tgz",
"integrity": "sha1-izgy7UDIfVH0e7I0kTppinVtGdI=",
"dev": true
},
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
@ -12416,8 +12456,7 @@
"version": "2.15.0", "version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==",
"dev": true, "dev": true
"optional": true
}, },
"nanoid": { "nanoid": {
"version": "3.2.0", "version": "3.2.0",
@ -12450,6 +12489,15 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true "dev": true
}, },
"neatequal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/neatequal/-/neatequal-1.0.0.tgz",
"integrity": "sha1-LuEhG8n6bkxVcV/SELsFYC6xrjs=",
"dev": true,
"requires": {
"varstream": "^0.3.2"
}
},
"needle": { "needle": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
@ -15227,6 +15275,12 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
}, },
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true
},
"qjobs": { "qjobs": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
@ -17240,6 +17294,18 @@
"strip-ansi": "^6.0.0" "strip-ansi": "^6.0.0"
} }
}, },
"string.fromcodepoint": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz",
"integrity": "sha1-jZeDM8C8klOPUPOD5IiPPlYZ1lM=",
"dev": true
},
"string.prototype.codepointat": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==",
"dev": true
},
"string.prototype.matchall": { "string.prototype.matchall": {
"version": "4.0.5", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz",
@ -17444,6 +17510,72 @@
"has-flag": "^3.0.0" "has-flag": "^3.0.0"
} }
}, },
"svg-pathdata": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-1.0.4.tgz",
"integrity": "sha1-emgTQqrH7/2NUq+6eZmRDJ2juVk=",
"dev": true,
"requires": {
"readable-stream": "~2.0.4"
},
"dependencies": {
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
"dev": true
},
"readable-stream": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "~1.0.0",
"process-nextick-args": "~1.0.6",
"string_decoder": "~0.10.x",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
}
}
},
"svg2ttf": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/svg2ttf/-/svg2ttf-4.3.0.tgz",
"integrity": "sha512-LZ0B7zzHWLWbzLzwaKGHQvPOuxCXLReIb3LSxFSGUy1gMw2Utk6KGNbTmbmRL6Rk1qDSmTixnDrQgnXaL9n0CA==",
"dev": true,
"requires": {
"argparse": "^1.0.6",
"cubic2quad": "^1.0.0",
"lodash": "^4.17.10",
"microbuffer": "^1.0.0",
"svgpath": "^2.1.5",
"xmldom": "~0.1.22"
}
},
"svgicons2svgfont": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/svgicons2svgfont/-/svgicons2svgfont-5.0.2.tgz",
"integrity": "sha1-BRGCPGSRvhp9VDKS4pqK5ietBAY=",
"dev": true,
"requires": {
"commander": "^2.9.0",
"neatequal": "^1.0.0",
"readable-stream": "^2.0.4",
"sax": "^1.1.5",
"string.fromcodepoint": "^0.2.1",
"string.prototype.codepointat": "^0.2.0",
"svg-pathdata": "^1.0.4"
}
},
"svgo": { "svgo": {
"version": "2.6.1", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.6.1.tgz", "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.6.1.tgz",
@ -17473,6 +17605,12 @@
} }
} }
}, },
"svgpath": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/svgpath/-/svgpath-2.5.0.tgz",
"integrity": "sha512-o/vohwqjUO9nDAh4rcjE3KaW/v//At8UJu2LJMybXidf5QLQLVA4bxH0//4YCsr+1H4Gw1Wi/Jc62ynzSBYidw==",
"dev": true
},
"swagger-client": { "swagger-client": {
"version": "3.17.0", "version": "3.17.0",
"resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.17.0.tgz", "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.17.0.tgz",
@ -18117,6 +18255,95 @@
} }
} }
}, },
"ttf2eot": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ttf2eot/-/ttf2eot-2.0.0.tgz",
"integrity": "sha1-jmM3pYWr0WCKDISVirSDzmn2ZUs=",
"dev": true,
"requires": {
"argparse": "^1.0.6",
"microbuffer": "^1.0.0"
}
},
"ttf2woff": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ttf2woff/-/ttf2woff-2.0.2.tgz",
"integrity": "sha512-X68badwBjAy/+itU49scLjXUL094up+rHuYk+YAOTTBYSUMOmLZ7VyhZJuqQESj1gnyLAC2/5V8Euv+mExmyPA==",
"dev": true,
"requires": {
"argparse": "^1.0.6",
"microbuffer": "^1.0.0",
"pako": "^1.0.0"
},
"dependencies": {
"pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
}
}
},
"ttf2woff2": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/ttf2woff2/-/ttf2woff2-2.0.3.tgz",
"integrity": "sha1-XgIK/m5kMofzrXaHq+0g/mVOsyk=",
"dev": true,
"requires": {
"bindings": "^1.2.1",
"bufferstreams": "^1.1.0",
"nan": "^2.1.0",
"node-gyp": "^3.0.3"
},
"dependencies": {
"node-gyp": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
"integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
"dev": true,
"requires": {
"fstream": "^1.0.0",
"glob": "^7.0.3",
"graceful-fs": "^4.1.2",
"mkdirp": "^0.5.0",
"nopt": "2 || 3",
"npmlog": "0 || 1 || 2 || 3 || 4",
"osenv": "0",
"request": "^2.87.0",
"rimraf": "2",
"semver": "~5.3.0",
"tar": "^2.0.0",
"which": "1"
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"dev": true,
"requires": {
"abbrev": "1"
}
},
"semver": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
"dev": true
},
"tar": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
"dev": true,
"requires": {
"block-stream": "*",
"fstream": "^1.0.12",
"inherits": "2"
}
}
}
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@ -18235,6 +18462,12 @@
"which-boxed-primitive": "^1.0.2" "which-boxed-primitive": "^1.0.2"
} }
}, },
"underscore": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.2.tgz",
"integrity": "sha512-ekY1NhRzq0B08g4bGuX4wd2jZx5GnKz6mKSqFL4nqBlfyMGiG10gDFhDTMEfYmDL6Jy0FUIZp7wiRB+0BP7J2g==",
"dev": true
},
"unicode-canonical-property-names-ecmascript": { "unicode-canonical-property-names-ecmascript": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@ -18463,6 +18696,41 @@
"builtins": "^1.0.3" "builtins": "^1.0.3"
} }
}, },
"varstream": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/varstream/-/varstream-0.3.2.tgz",
"integrity": "sha1-GKxklHZfP/GjWtmkvgU77BiKXeE=",
"dev": true,
"requires": {
"readable-stream": "^1.0.33"
},
"dependencies": {
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
}
}
},
"vary": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -18538,6 +18806,32 @@
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.1.tgz",
"integrity": "sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ==" "integrity": "sha512-3ux37gEX670UUphBF9AMCq8XM6iQ8Ac6A+DSRRjDoRBm1ufCkaCDdNVbaqq60PsEkdNlLKrGtv/YBP4EJXqNtQ=="
}, },
"webfonts-generator": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/webfonts-generator/-/webfonts-generator-0.4.0.tgz",
"integrity": "sha1-X4n8gccWDm4Mu8m3OH5CpYUf2kY=",
"dev": true,
"requires": {
"handlebars": "^4.0.5",
"mkdirp": "^0.5.0",
"q": "^1.1.2",
"svg2ttf": "^4.0.0",
"svgicons2svgfont": "^5.0.0",
"ttf2eot": "^2.0.0",
"ttf2woff": "^2.0.1",
"ttf2woff2": "^2.0.3",
"underscore": "^1.7.0",
"url-join": "^1.1.0"
},
"dependencies": {
"url-join": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-1.1.0.tgz",
"integrity": "sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg=",
"dev": true
}
}
},
"webpack": { "webpack": {
"version": "5.50.0", "version": "5.50.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.50.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.50.0.tgz",
@ -19359,6 +19653,12 @@
"repeat-string": "^1.5.2" "repeat-string": "^1.5.2"
} }
}, },
"xmldom": {
"version": "0.1.31",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz",
"integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==",
"dev": true
},
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

@ -59,6 +59,7 @@
"theo": "^8.1.5", "theo": "^8.1.5",
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"typescript": "~4.2.4", "typescript": "~4.2.4",
"webfonts-generator": "^0.4.0",
"webpack-bundle-analyzer": "^4.4.2" "webpack-bundle-analyzer": "^4.4.2"
}, },
"dependencies": { "dependencies": {
@ -146,6 +147,7 @@
"build": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --configuration production --named-chunks --extract-css --source-map", "build": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --configuration production --named-chunks --extract-css --source-map",
"build:watch": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --watch --named-chunks --extract-css", "build:watch": "node --max_old_space_size=4096 ./node_modules/@angular/cli/bin/ng build --watch --named-chunks --extract-css",
"tokens:generate": "theo src/app/spot/styles/tokens/tokens.yml --transform web --format sass,json --dest src/app/spot/styles/tokens/dist", "tokens:generate": "theo src/app/spot/styles/tokens/tokens.yml --transform web --format sass,json --dest src/app/spot/styles/tokens/dist",
"icon-font:generate": "node ./src/app/spot/icon-font/generate.js ./src/app/spot/icon-font",
"preserve": "./scripts/link_plugin_placeholder.js", "preserve": "./scripts/link_plugin_placeholder.js",
"serve": "NG_PERSISTENT_BUILD_CACHE=1 node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --public-host http://localhost:4200", "serve": "NG_PERSISTENT_BUILD_CACHE=1 node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --public-host http://localhost:4200",
"serve:test": "NG_PERSISTENT_BUILD_CACHE=1 node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --disable-host-check --public-host http://frontend-test:4200", "serve:test": "NG_PERSISTENT_BUILD_CACHE=1 node --max_old_space_size=8096 ./node_modules/@angular/cli/bin/ng serve --host 0.0.0.0 --disable-host-check --public-host http://frontend-test:4200",

@ -29,7 +29,7 @@
</div> </div>
<form <form
(submit)="onSubmit($event)" (submit)="onSubmit($event)"
class="spot-body" class="spot-body op-project-include--body"
> >
<spot-text-field <spot-text-field
[disabled]="!(areProjectsLoaded$ | async)" [disabled]="!(areProjectsLoaded$ | async)"
@ -41,19 +41,21 @@
> >
<span <span
slot="before" slot="before"
class="op-icon icon-search" class="spot-icon spot-icon_search"
></span> ></span>
</spot-text-field> </spot-text-field>
<ng-container *ngIf="(areProjectsLoaded$ | async); else loadingTemplate"> <ng-container *ngIf="(areProjectsLoaded$ | async); else loadingTemplate">
<ul <ul
class="op-project-include--list"
op-project-list op-project-list
[projects]="projects$ | async" [projects]="projects$ | async"
[selected]="selectedProjects" [selected]="selectedProjects"
[query]="query"
(update)="selectedProjects = $event" (update)="selectedProjects = $event"
data-qa-selector="project-include-list" data-qa-selector="project-include-list"
></ul> ></ul>
</ng-container> </ng-container>
<div class="spot-action-bar"> <div class="spot-action-bar">
<div class="spot-action-bar--left"></div> <div class="spot-action-bar--left"></div>
@ -74,40 +76,41 @@
</button> </button>
</div> </div>
</div> </div>
<ng-template #loadingTemplate> </form>
<op-content-loader </ng-container>
class="op-project-include--loading" </spot-drop-modal>
data-qa-selector="op-project-include--loading"
viewBox="0 0 200 140"
>
<svg:rect x="0" width="12" height="12" rx="1" />
<svg:rect x="15" width="100%" height="12" rx="1" />
<svg:rect x="10" y="16" width="12" height="12" rx="1" /> <ng-template #loadingTemplate>
<svg:rect x="25" y="16" width="100%" height="12" rx="1" /> <op-content-loader
class="op-project-include--loading"
data-qa-selector="op-project-include--loading"
viewBox="0 0 200 140"
>
<svg:rect x="0" width="12" height="12" rx="1" />
<svg:rect x="15" width="100%" height="12" rx="1" />
<svg:rect x="10" y="32" width="12" height="12" rx="1" /> <svg:rect x="10" y="16" width="12" height="12" rx="1" />
<svg:rect x="25" y="32" width="100%" height="12" rx="1" /> <svg:rect x="25" y="16" width="100%" height="12" rx="1" />
<svg:rect x="20" y="48" width="12" height="12" rx="1" /> <svg:rect x="10" y="32" width="12" height="12" rx="1" />
<svg:rect x="35" y="48" width="100%" height="12" rx="1" /> <svg:rect x="25" y="32" width="100%" height="12" rx="1" />
<svg:rect x="20" y="64" width="12" height="12" rx="1" /> <svg:rect x="20" y="48" width="12" height="12" rx="1" />
<svg:rect x="35" y="64" width="100%" height="12" rx="1" /> <svg:rect x="35" y="48" width="100%" height="12" rx="1" />
<svg:rect x="0" y="80" width="12" height="12" rx="1" /> <svg:rect x="20" y="64" width="12" height="12" rx="1" />
<svg:rect x="15" y="80" width="100%" height="12" rx="1" /> <svg:rect x="35" y="64" width="100%" height="12" rx="1" />
<svg:rect x="10" y="96" width="12" height="12" rx="1" /> <svg:rect x="0" y="80" width="12" height="12" rx="1" />
<svg:rect x="25" y="96" width="100%" height="12" rx="1" /> <svg:rect x="15" y="80" width="100%" height="12" rx="1" />
<svg:rect x="10" y="112" width="12" height="12" rx="1" /> <svg:rect x="10" y="96" width="12" height="12" rx="1" />
<svg:rect x="25" y="112" width="100%" height="12" rx="1" /> <svg:rect x="25" y="96" width="100%" height="12" rx="1" />
<svg:rect x="0" y="128" width="12" height="12" rx="1" /> <svg:rect x="10" y="112" width="12" height="12" rx="1" />
<svg:rect x="15" y="128" width="100%" height="12" rx="1" /> <svg:rect x="25" y="112" width="100%" height="12" rx="1" />
</op-content-loader>
</ng-template> <svg:rect x="0" y="128" width="12" height="12" rx="1" />
</form> <svg:rect x="15" y="128" width="100%" height="12" rx="1" />
</ng-container> </op-content-loader>
</spot-drop-modal> </ng-template>

@ -1,9 +1,9 @@
@import '../../../spot/styles/sass/variables' @import '../../../spot/styles/sass/variables'
@import '../../../spot/styles/sass/typography'
\::ng-deep .spot-drop-modal--body \::ng-deep .spot-drop-modal--body
width: 460px
max-width: 100vw @media screen and #{$spot-mq-drop-modal-in-context}
width: 460px
.op-project-include .op-project-include
&--header &--header
@ -17,5 +17,18 @@
padding: 0 padding: 0
@include spot-subheader-big @include spot-subheader-big
&--body
flex-shrink: 1
flex-basis: 100%
overflow: hidden
@media screen and #{$spot-mq-drop-modal-in-context}
max-height: 40vh
&--list
overflow-y: auto
flex-shrink: 1
flex-basis: 100%
&--loading &--loading
padding: 0 15px 0 15px padding: 0 $spot-spacing-1 0 $spot-spacing-1

@ -2,16 +2,22 @@
class="spot-list--item" class="spot-list--item"
*ngFor="let project of projects" *ngFor="let project of projects"
> >
<label class="spot-list--item-action op-project-list--item-action"> <label
class="spot-list--item-action op-project-list--item-action"
[ngClass]="{ 'spot-list--item-action_disabled': !project.found }"
>
<spot-checkbox <spot-checkbox
*ngFor="let checked of [isChecked(project.href)]" *ngFor="let checked of [isChecked(project.href)]"
[checked]="checked" [checked]="checked"
(change)="changeSelected(project.href)" (change)="changeSelected(project.href)"
[disabled]="project.href === currentProjectHref" [disabled]="!project.found || project.href === currentProjectHref"
[attr.data-qa-project-include-id]="project.id" [attr.data-qa-project-include-id]="project.id"
[attr.data-qa-project-include-checked]="checked ? 1 : 0" [attr.data-qa-project-include-checked]="checked ? 1 : 0"
></spot-checkbox> ></spot-checkbox>
<div class="spot-list--item-title">{{ project.name }}</div> <div
class="spot-list--item-title"
[opSearchHighlight]="query"
>{{ project.name }}</div>
</label> </label>
<ul <ul
@ -19,6 +25,7 @@
op-project-list op-project-list
[projects]="project.children" [projects]="project.children"
[selected]="selected" [selected]="selected"
[query]="query"
(update)="updateSelected($event)" (update)="updateSelected($event)"
></ul> ></ul>
</li> </li>

@ -27,6 +27,8 @@ export class OpProjectListComponent {
@Input() selected:string[] = []; @Input() selected:string[] = [];
@Input() query = '';
public get currentProjectHref():string|null { public get currentProjectHref():string|null {
return this.currentProjectService.apiv3Path; return this.currentProjectService.apiv3Path;
} }

@ -0,0 +1,2 @@
.op-search-highlight
color: $spot-color-main

@ -0,0 +1,43 @@
import {
AfterViewChecked,
Directive,
ElementRef,
Input,
} from '@angular/core';
@Directive({
selector: '[opSearchHighlight]',
})
export class OpSearchHighlightDirective implements AfterViewChecked {
@Input('opSearchHighlight') public query = '';
constructor(readonly elementRef:ElementRef) { }
ngAfterViewChecked():void {
if (!this.query) {
return;
}
const el = this.elementRef.nativeElement as HTMLElement;
const textNode = Array.from(el.childNodes).find((n:Node) => n.nodeType === n.TEXT_NODE) as Node;
const content = textNode?.textContent || '';
if (!content) {
return;
}
const query = this.query.toLowerCase();
const startIndex = content.toLowerCase().indexOf(query);
if (startIndex < 0) {
return;
}
const start = content.slice(0, startIndex);
const result = content.slice(startIndex, startIndex + query.length);
const end = content.slice(startIndex + query.length);
const newNode = document.createElement('span');
newNode.innerHTML = `${start}<span class="op-search-highlight">${result}</span>${end}`;
el.replaceChild(newNode, textNode);
}
}

@ -64,6 +64,7 @@ import {
highlightColSelector, highlightColSelector,
OpHighlightColDirective, OpHighlightColDirective,
} from './directives/highlight-col/highlight-col.directive'; } from './directives/highlight-col/highlight-col.directive';
import { OpSearchHighlightDirective } from './directives/search-highlight.directive';
import { CopyToClipboardDirective } from './components/copy-to-clipboard/copy-to-clipboard.directive'; import { CopyToClipboardDirective } from './components/copy-to-clipboard/copy-to-clipboard.directive';
import { OpDateTimeComponent } from './components/date/op-date-time.component'; import { OpDateTimeComponent } from './components/date/op-date-time.component';
@ -168,6 +169,8 @@ export function bootstrapModule(injector:Injector) {
// Table highlight // Table highlight
OpHighlightColDirective, OpHighlightColDirective,
OpSearchHighlightDirective,
ResizerComponent, ResizerComponent,
TablePaginationComponent, TablePaginationComponent,
@ -230,6 +233,8 @@ export function bootstrapModule(injector:Injector) {
TablePaginationComponent, TablePaginationComponent,
SortHeaderDirective, SortHeaderDirective,
OpSearchHighlightDirective,
// Zen mode button // Zen mode button
ZenModeButtonComponent, ZenModeButtonComponent,

@ -2,13 +2,16 @@ import {
Component, Component,
ElementRef, ElementRef,
EventEmitter, EventEmitter,
ViewChild,
forwardRef, forwardRef,
HostBinding, HostBinding,
Input, Input,
Output, Output,
ViewChild,
} from '@angular/core'; } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import {
ControlValueAccessor,
NG_VALUE_ACCESSOR,
} from '@angular/forms';
export type SpotCheckboxState = true|false|null; export type SpotCheckboxState = true|false|null;
@ -34,36 +37,37 @@ export class SpotCheckboxComponent implements ControlValueAccessor {
@Input() public checked = false; @Input() public checked = false;
onStateChange() { onStateChange():void {
const value = this.input.nativeElement.checked; const value = (this.input.nativeElement as HTMLInputElement).checked;
this.checkedChange.emit(value); this.checkedChange.emit(value);
this.onChange(value); this.onChange(value);
this.onTouched(value); this.onTouched(value);
} }
writeValue(value:SpotCheckboxState) { writeValue(value:SpotCheckboxState):void {
// This is set in a timeout because the initial value is set before the template is ready, // This is set in a timeout because the initial value is set before the template is ready,
// which causes the input nativeElement to not be available yet. // which causes the input nativeElement to not be available yet.
setTimeout(() => { setTimeout(() => {
const input = this.input.nativeElement; const input = this.input.nativeElement as HTMLInputElement;
if (value === null) { if (value === null) {
input.indeterminate = true; input.indeterminate = true;
} else { } else {
input.indeterminate = false; input.indeterminate = false;
} }
this.checked = !!value; this.checked = !!value;
}); });
} }
onChange = (_:SpotCheckboxState) => {}; onChange = (_:SpotCheckboxState):void => {};
onTouched = (_:SpotCheckboxState) => {};
onTouched = (_:SpotCheckboxState):void => {};
registerOnChange(fn:any) { registerOnChange(fn:(_:SpotCheckboxState) => void):void {
this.onChange = fn; this.onChange = fn;
} }
registerOnTouched(fn:any) { registerOnTouched(fn:(_:SpotCheckboxState) => void):void {
this.onTouched = fn; this.onTouched = fn;
} }
} }

@ -20,6 +20,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
}) })
export class SpotChipFieldComponent implements ControlValueAccessor { export class SpotChipFieldComponent implements ControlValueAccessor {
@HostBinding('class.spot-chip-field') public classNameChipField = true; @HostBinding('class.spot-chip-field') public classNameChipField = true;
@HostBinding('class.spot-text-field') public classNameTextField = true; @HostBinding('class.spot-text-field') public classNameTextField = true;
@HostListener('click') public onParentClick() { @HostListener('click') public onParentClick() {
@ -29,27 +30,30 @@ export class SpotChipFieldComponent implements ControlValueAccessor {
@ViewChild('input') public input:ElementRef; @ViewChild('input') public input:ElementRef;
@Input() name = `spot-chip-field-${+(new Date())}`; @Input() name = `spot-chip-field-${+(new Date())}`;
@Input() disabled = false; @Input() disabled = false;
@Input() public placeholder = ''; @Input() public placeholder = '';
@Input('value') public _value:string[] = []; @Input('value') public _value:string[] = [];
public textValue = ''; textValue = '';
public get value():string[] { get value():string[] {
return this._value; return this._value;
} }
public set value(value:string[]) { set value(value:string[]) {
this._value = value; this._value = value;
this.onChange(value); this.onChange(value);
this.onTouched(value); this.onTouched(value);
} }
public remove(i:number) { remove(i:number):void {
this.value = this.value.slice(0, i).concat(this.value.slice(i + 1)); this.value = this.value.slice(0, i).concat(this.value.slice(i + 1));
} }
public onBackspace(e:KeyboardEvent) { onBackspace(e:KeyboardEvent):void {
if (this.textValue !== '') { if (this.textValue !== '') {
return; return;
} }
@ -59,7 +63,7 @@ export class SpotChipFieldComponent implements ControlValueAccessor {
this.value = this.value.slice(0, this.value.length - 1); this.value = this.value.slice(0, this.value.length - 1);
} }
public onEnter(e:KeyboardEvent) { onEnter(e:KeyboardEvent):void {
e.stopPropagation(); e.stopPropagation();
if (this.textValue === '') { if (this.textValue === '') {
@ -76,19 +80,19 @@ export class SpotChipFieldComponent implements ControlValueAccessor {
this.textValue = ''; this.textValue = '';
} }
writeValue(value:string[]) { writeValue(value:string[]):void {
this.value = value; this.value = value;
} }
onChange = (_:string[]) => {}; onChange = (_:string[]):void => {};
onTouched = (_:string[]) => {};
onTouched = (_:string[]):void => {};
registerOnChange(fn:any) { registerOnChange(fn:(_:string[]) => void):void {
this.onChange = fn; this.onChange = fn;
} }
registerOnTouched(fn:any) { registerOnTouched(fn:(_:string[]) => void):void {
this.onTouched = fn; this.onTouched = fn;
} }
} }

@ -1,8 +0,0 @@
<ng-content select="[slot=trigger]"></ng-content>
<div
[ngClass]="['spot-drop-modal--body', 'spot-body', alignmentClass]"
(click)="onBodyClick($event)"
(keypress.escape)="close()"
>
<ng-content select="[slot=body]"></ng-content>
</div>

@ -0,0 +1,17 @@
<ng-content select="[slot=trigger]"></ng-content>
<div
[ngClass]="['spot-drop-modal--body', 'spot-body', alignmentClass]"
(click)="onBodyClick($event)"
cdkTrapFocus
>
<ng-content select="[slot=body]"></ng-content>
<button
class="spot-drop-modal--close-button spot-button"
type="button"
[attr.aria-label]="text.close"
(click)="close()"
>
<span class="spot-icon spot-icon_close"></span>
</button>
</div>

@ -6,6 +6,8 @@ import {
EventEmitter, EventEmitter,
OnDestroy, OnDestroy,
} from '@angular/core'; } from '@angular/core';
import { KeyCodes } from 'core-app/shared/helpers/keyCodes.enum';
import { I18nService } from 'core-app/core/i18n/i18n.service';
enum SpotDropModalAlignmentOption { enum SpotDropModalAlignmentOption {
BottomCenter = 'bottom-center', BottomCenter = 'bottom-center',
@ -25,8 +27,12 @@ export class SpotDropModalComponent implements OnDestroy {
@HostBinding('class.spot-drop-modal_opened') public _open = false; @HostBinding('class.spot-drop-modal_opened') public _open = false;
@Output() closed = new EventEmitter<void>();
@Input() public alignment:SpotDropModalAlignmentOption = SpotDropModalAlignmentOption.BottomLeft;
@Input('open') @Input('open')
public set open(value:boolean) { set open(value:boolean) {
this._open = value; this._open = value;
if (this._open) { if (this._open) {
@ -36,39 +42,51 @@ export class SpotDropModalComponent implements OnDestroy {
*/ */
setTimeout(() => { setTimeout(() => {
document.body.addEventListener('click', this.closeEventListener); document.body.addEventListener('click', this.closeEventListener);
}) document.body.addEventListener('keydown', this.escapeListener);
});
} else { } else {
document.body.removeEventListener('click', this.closeEventListener); document.body.removeEventListener('click', this.closeEventListener);
document.body.removeEventListener('click', this.escapeListener);
this.closed.emit(); this.closed.emit();
} }
} }
public get open():boolean { get open():boolean {
return this._open; return this._open;
} }
@Input('alignment') public alignment:SpotDropModalAlignmentOption = SpotDropModalAlignmentOption.BottomCenter; get alignmentClass():string {
get alignmentClass() {
return `spot-drop-modal--body_${this.alignment}`; return `spot-drop-modal--body_${this.alignment}`;
} }
@Output() closed = new EventEmitter<void>(); public text = {
close: this.i18n.t('js.spot.drop_modal.close'),
};
private closeEventListener = this.close.bind(this); constructor(readonly i18n:I18nService) {}
public close():void { close():void {
this.open = false; this.open = false;
} }
public onBodyClick(e:MouseEvent) { onBodyClick(e:MouseEvent):void {
// We stop propagation here so that clicks inside the body do not // We stop propagation here so that clicks inside the body do not
// close the modal when the event reaches the document body // close the modal when the event reaches the document body
e.stopPropagation(); e.stopPropagation();
} }
public ngOnDestroy():void { ngOnDestroy():void {
document.body.removeEventListener('click', this.closeEventListener); document.body.removeEventListener('click', this.closeEventListener);
document.body.removeEventListener('click', this.escapeListener);
} }
}
private closeEventListener = this.close.bind(this) as () => void;
private onEscape = (evt:KeyboardEvent) => {
if (evt.keyCode === KeyCodes.ESCAPE) {
this.close();
}
};
private escapeListener = this.onEscape.bind(this) as () => void;
}

@ -1,7 +0,0 @@
<button
*ngIf="removable"
class="spot-filter-chip--remove"
type="button"
(click)="remove.emit()"
>x</button>
<div class="spot-filter-chip--title">{{ title }}</div>

@ -0,0 +1,16 @@
<button
*ngIf="removable"
class="spot-filter-chip--remove"
type="button"
(click)="remove.emit()"
[attr.aria-label]="text.remove"
>
<span class="spot-icon spot-icon_close"></span>
</button>
<div class="spot-filter-chip--title">
<span
*ngIf="icon"
[ngClass]="iconClasses"
></span>
{{ title }}
</div>

@ -5,6 +5,7 @@ import {
Input, Input,
Output, Output,
} from '@angular/core'; } from '@angular/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
@Component({ @Component({
selector: 'spot-filter-chip', selector: 'spot-filter-chip',
@ -14,7 +15,23 @@ export class SpotFilterChipComponent {
@HostBinding('class.spot-filter-chip') public className = true; @HostBinding('class.spot-filter-chip') public className = true;
@Input() removable = true; @Input() removable = true;
@Input() title = ''; @Input() title = '';
@Input() icon = '';
@Output() remove = new EventEmitter<void>(); @Output() remove = new EventEmitter<void>();
public text = {
remove: this.i18n.t('js.spot.filter_chip.remove'),
};
public get iconClasses():string[] {
return [
'spot-icon',
`spot-icon_${this.icon}`,
];
}
constructor(readonly i18n:I18nService) {}
} }

@ -28,15 +28,18 @@ export class SpotTextFieldComponent implements ControlValueAccessor {
@ViewChild('input') public input:ElementRef; @ViewChild('input') public input:ElementRef;
@Input() name = `spot-text-field-${+(new Date())}`; @Input() name = `spot-text-field-${+(new Date())}`;
@Input() disabled = false; @Input() disabled = false;
@Input() public placeholder = ''; @Input() public placeholder = '';
@Input('value') public _value = ''; @Input('value') public _value = '';
public get value():string { get value():string {
return this._value; return this._value;
} }
public set value(value:string) { set value(value:string) {
this._value = value; this._value = value;
this.onChange(value); this.onChange(value);
this.onTouched(value); this.onTouched(value);
@ -46,15 +49,15 @@ export class SpotTextFieldComponent implements ControlValueAccessor {
this.value = value; this.value = value;
} }
onChange = (_:string) => {}; onChange = (_:string):void => {};
onTouched = (_:string) => {};
registerOnChange(fn:any) { onTouched = (_:string):void => {};
registerOnChange(fn:(_:string) => void):void {
this.onChange = fn; this.onChange = fn;
} }
registerOnTouched(fn:any) { registerOnTouched(fn:(_:string) => void):void {
this.onTouched = fn; this.onTouched = fn;
} }
} }

@ -28,7 +28,9 @@ export class SpotToggleComponent<T> implements ControlValueAccessor {
@Output() checkedChange = new EventEmitter<boolean>(); @Output() checkedChange = new EventEmitter<boolean>();
@Input() options:SpotToggleOption<T>[] = []; @Input() options:SpotToggleOption<T>[] = [];
@Input() name = `spot-toggle-${+(new Date())}`; @Input() name = `spot-toggle-${+(new Date())}`;
@Input('value') public _value:T; @Input('value') public _value:T;
public get value():T { public get value():T {
@ -41,19 +43,19 @@ export class SpotToggleComponent<T> implements ControlValueAccessor {
this.onTouched(value); this.onTouched(value);
} }
writeValue(value:T) { writeValue(value:T):void {
this.value = value; this.value = value;
} }
onChange = (_:T) => {}; onChange = (_:T):void => {};
onTouched = (_:T) => {};
registerOnChange(fn:any) { onTouched = (_:T):void => {};
registerOnChange(fn:(_:T) => void):void {
this.onChange = fn; this.onChange = fn;
} }
registerOnTouched(fn:any) { registerOnTouched(fn:(_:T) => void):void {
this.onTouched = fn; this.onTouched = fn;
} }
} }

@ -12,16 +12,14 @@ This license can also be found at this permalink: http://creativecommons.org/lic
## Structure ## Structure
This directory is the source for the generated icon font in the Rails `app/assets/font` directory. This directory is the source for the generated icon font in the Rails `frontend/src/global_styles/fonts` directory.
Since it seldomly changes, it is only rebuilt manually and on demand. Since it seldomly changes, it is only rebuilt manually and on demand.
## Rebuilding ## Rebuilding
To rebuild the font (e.g., after changing icons in the source `app/assets/fonts/openproject_icon/src` directory), use the node script `generate.js`. To rebuild the font (e.g., after changing icons in the source `src/` directory under this README), use the node script `generate.js`.
``` ```
$ cd vendor/openproject-icon-font/ $ cd frontend/src/app/spot/icon-font/
$ node generate.js $ node generate.js
``` ```
To use, you need to install the webfonts generator package with: `npm install webfonts-generator`.

@ -0,0 +1,57 @@
#!/usr/bin/env node
const webfontsGenerator = require('webfonts-generator');
const path = require('path');
const glob = require("glob")
const TEMPLATE_DIR = path.resolve(process.argv[2]);
const CSS_FONT_URL = "../../../../../frontend/src/assets/fonts/openproject_icon/";
const FONT_DESTINATION = path.resolve(__dirname, '..', '..', '..', '..', '..', 'frontend', 'src', 'assets', 'fonts', 'openproject_icon');
const files = glob.sync(path.join(TEMPLATE_DIR, 'src/*.svg'));
webfontsGenerator({
files,
"fontName": "openproject-icon-font",
"cssFontsUrl": CSS_FONT_URL,
"dest": FONT_DESTINATION,
"cssDest": path.join(
path.resolve(__dirname, '..', 'styles', 'sass', 'common'),
'icon.sass',
),
"cssTemplate": path.join(TEMPLATE_DIR, "icon.template.sass"),
"types": ['woff2', 'woff'],
"fixedWidth": true,
"descent": 100
}, function(error) {
if (error) {
console.log('Failed to build icon font. ', error);
}
});
webfontsGenerator({
files,
"fontName": "openproject-icon-font",
"cssFontsUrl": CSS_FONT_URL,
"dest": FONT_DESTINATION,
"cssDest": path.join(
path.resolve(__dirname, '..', '..', '..', '..', '..', 'frontend', 'src', 'global_styles', 'fonts'),
'_openproject_icon_definitions.sass',
),
"cssTemplate": path.join(TEMPLATE_DIR, "openproject-icon-font.template.sass"),
"classPrefix": "icon-",
"baseSelector": ".icon",
"html": true,
"htmlDest": path.join(
path.resolve(__dirname, '..', '..', '..', '..', '..', 'frontend', 'src', 'global_styles', 'fonts'),
'_openproject_icon_font.lsg',
),
"htmlTemplate": path.join(TEMPLATE_DIR, "openproject-icon-font.template.lsg"),
"types": ['woff2', 'woff'],
"fixedWidth": true,
"descent": 100
}, function(error) {
if (error) {
console.log('Failed to build icon font. ', error);
}
});

@ -0,0 +1,28 @@
.spot-icon
display: inline-flex
justify-content: center
align-items: center
&::before
font-family: "openproject-icon-font"
font-style: normal
font-weight: normal
font-variant: normal
font-size: inherit
text-transform: none
text-decoration: none
display: block
speak: none
line-height: 1
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
{{#each codepoints}}
@mixin spot-icon-{{@key}}
content: "\\{{this}}"
.spot-icon_{{@key}}:before
content: "\\{{this}}"
{{/each}}

@ -0,0 +1,9 @@
{{#each codepoints}}
@mixin icon-mixin-{{@key}}
content: "\\{{this}}"
.{{../classPrefix}}{{@key}}:before
content: "\\{{this}}"
{{/each}}

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 129 B

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 187 B

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

Before

Width:  |  Height:  |  Size: 231 B

After

Width:  |  Height:  |  Size: 231 B

Before

Width:  |  Height:  |  Size: 101 B

After

Width:  |  Height:  |  Size: 101 B

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 610 B

Before

Width:  |  Height:  |  Size: 388 B

After

Width:  |  Height:  |  Size: 388 B

Before

Width:  |  Height:  |  Size: 125 B

After

Width:  |  Height:  |  Size: 125 B

Before

Width:  |  Height:  |  Size: 683 B

After

Width:  |  Height:  |  Size: 683 B

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 488 B

After

Width:  |  Height:  |  Size: 488 B

Before

Width:  |  Height:  |  Size: 283 B

After

Width:  |  Height:  |  Size: 283 B

Before

Width:  |  Height:  |  Size: 582 B

After

Width:  |  Height:  |  Size: 582 B

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 450 B

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 358 B

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 257 B

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 285 B

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 166 B

Before

Width:  |  Height:  |  Size: 359 B

After

Width:  |  Height:  |  Size: 359 B

Before

Width:  |  Height:  |  Size: 167 B

After

Width:  |  Height:  |  Size: 167 B

Before

Width:  |  Height:  |  Size: 244 B

After

Width:  |  Height:  |  Size: 244 B

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 153 B

Before

Width:  |  Height:  |  Size: 172 B

After

Width:  |  Height:  |  Size: 172 B

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 166 B

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 382 B

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

Before

Width:  |  Height:  |  Size: 161 B

After

Width:  |  Height:  |  Size: 161 B

Before

Width:  |  Height:  |  Size: 201 B

After

Width:  |  Height:  |  Size: 201 B

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 677 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 284 B

After

Width:  |  Height:  |  Size: 284 B

Before

Width:  |  Height:  |  Size: 514 B

After

Width:  |  Height:  |  Size: 514 B

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

Before

Width:  |  Height:  |  Size: 583 B

After

Width:  |  Height:  |  Size: 583 B

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 564 B

After

Width:  |  Height:  |  Size: 564 B

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save