Implement new UI component for icons #230
- Introduce `AppIcon.vue`, offering improved performance over the previous `fort-awesome` dependency. This implementation reduces bundle size by 67.31KB (tested for web using `npm run build -- --mode prod`). - Migrate Font Awesome 5 icons to Font Awesome 6. This commit facilitates migration to Vue 3.0 (#230) and ensures no Vue component remains tightly bound to a specific Vue version, enhancing code portability. Font Awesome license is not included because Font Awesome revokes its right: > "Attribution is no longer required as of Font Awesome 3.0" > > Sources: > > - https://fontawesome.com/v4/license/ (archived: https://web.archive.org/web/20231003213441/https://fontawesome.com/v4/license/, https://archive.ph/Yy9j5) > - https://github.com/FortAwesome/Font-Awesome/wiki (archived: https://web.archive.org/web/20231003214646/https://github.com/FortAwesome/Font-Awesome/wiki, https://archive.ph/C6sXv) This commit removes following third-party production dependencies: - `@fortawesome/vue-fontawesome` - `@fortawesome/free-solid-svg-icons` - `@fortawesome/free-regular-svg-icons` - `@fortawesome/free-brands-svg-icons` - `@fortawesome/fontawesome-svg-core`
1
src/presentation/assets/icons/battery-full.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm368 96H96V320H448V192z"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
src/presentation/assets/icons/battery-half.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm208 96H96V320H288V192z"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
src/presentation/assets/icons/circle-info.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
1
src/presentation/assets/icons/copy.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M208 0H332.1c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9V336c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V48c0-26.5 21.5-48 48-48zM48 128h80v64H64V448H256V416h64v48c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48z"/></svg>
|
||||
|
After Width: | Height: | Size: 365 B |
1
src/presentation/assets/icons/desktop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V288H64V64H512z"/></svg>
|
||||
|
After Width: | Height: | Size: 342 B |
1
src/presentation/assets/icons/face-smile.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm177.6 62.1C192.8 334.5 218.8 352 256 352s63.2-17.5 78.4-33.9c9-9.7 24.2-10.4 33.9-1.4s10.4 24.2 1.4 33.9c-22 23.8-60 49.4-113.6 49.4s-91.7-25.5-113.6-49.4c-9-9.7-8.4-24.9 1.4-33.9s24.9-8.4 33.9 1.4zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 495 B |
1
src/presentation/assets/icons/file-arrow-down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM216 232V334.1l31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31V232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
1
src/presentation/assets/icons/floppy-disk.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V173.3c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32H64zm0 96c0-17.7 14.3-32 32-32H288c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V128zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>
|
||||
|
After Width: | Height: | Size: 407 B |
1
src/presentation/assets/icons/folder-open.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M384 480h48c11.4 0 21.9-6 27.6-15.9l112-192c5.8-9.9 5.8-22.1 .1-32.1S555.5 224 544 224H144c-11.4 0-21.9 6-27.6 15.9L48 357.1V96c0-8.8 7.2-16 16-16H181.5c4.2 0 8.3 1.7 11.3 4.7l26.5 26.5c21 21 49.5 32.8 79.2 32.8H416c8.8 0 16 7.2 16 16v32h48V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H87.7 384z"/></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
1
src/presentation/assets/icons/folder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 96C0 60.7 28.7 32 64 32H196.1c19.1 0 37.4 7.6 50.9 21.1L289.9 96H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16H286.6c-10.6 0-20.8-4.2-28.3-11.7L213.1 87c-4.5-4.5-10.6-7-17-7H64z"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
1
src/presentation/assets/icons/github.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/presentation/assets/icons/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/presentation/assets/icons/left-right.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M504.3 273.6c4.9-4.5 7.7-10.9 7.7-17.6s-2.8-13-7.7-17.6l-112-104c-7-6.5-17.2-8.2-25.9-4.4s-14.4 12.5-14.4 22l0 56-192 0 0-56c0-9.5-5.7-18.2-14.4-22s-18.9-2.1-25.9 4.4l-112 104C2.8 243 0 249.3 0 256s2.8 13 7.7 17.6l112 104c7 6.5 17.2 8.2 25.9 4.4s14.4-12.5 14.4-22l0-56 192 0 0 56c0 9.5 5.7 18.2 14.4 22s18.9 2.1 25.9-4.4l112-104z"/></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
1
src/presentation/assets/icons/magnifying-glass.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
1
src/presentation/assets/icons/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>
|
||||
|
After Width: | Height: | Size: 257 B |
1
src/presentation/assets/icons/tag.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
1
src/presentation/assets/icons/user-secret.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 16c-6.7 0-10.8-2.8-15.5-6.1C201.9 5.4 194 0 176 0c-30.5 0-52 43.7-66 89.4C62.7 98.1 32 112.2 32 128c0 14.3 25 27.1 64.6 35.9c-.4 4-.6 8-.6 12.1c0 17 3.3 33.2 9.3 48H45.4C38 224 32 230 32 237.4c0 1.7 .3 3.4 1 5l38.8 96.9C28.2 371.8 0 423.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7c0-58.5-28.2-110.4-71.7-143L415 242.4c.6-1.6 1-3.3 1-5c0-7.4-6-13.4-13.4-13.4H342.7c6-14.8 9.3-31 9.3-48c0-4.1-.2-8.1-.6-12.1C391 155.1 416 142.3 416 128c0-15.8-30.7-29.9-78-38.6C324 43.7 302.5 0 272 0c-18 0-25.9 5.4-32.5 9.9c-4.8 3.3-8.8 6.1-15.5 6.1zm56 208H267.6c-16.5 0-31.1-10.6-36.3-26.2c-2.3-7-12.2-7-14.5 0c-5.2 15.6-19.9 26.2-36.3 26.2H168c-22.1 0-40-17.9-40-40V169.6c28.2 4.1 61 6.4 96 6.4s67.8-2.3 96-6.4V184c0 22.1-17.9 40-40 40zm-88 96l16 32L176 480 128 288l64 32zm128-32L272 480 240 352l16-32 64-32z"/></svg>
|
||||
|
After Width: | Height: | Size: 934 B |
1
src/presentation/assets/icons/xmark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -1,4 +1,3 @@
|
||||
import { IconBootstrapper } from './Modules/IconBootstrapper';
|
||||
import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper';
|
||||
import { VueBootstrapper } from './Modules/VueBootstrapper';
|
||||
import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator';
|
||||
@@ -14,7 +13,6 @@ export class ApplicationBootstrapper implements IVueBootstrapper {
|
||||
|
||||
private static getAllBootstrappers(): IVueBootstrapper[] {
|
||||
return [
|
||||
new IconBootstrapper(),
|
||||
new VueBootstrapper(),
|
||||
new RuntimeSanityValidator(),
|
||||
new AppInitializationLogger(),
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||
/** BRAND ICONS (PREFIX: fab) */
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
/** REGULAR ICONS (PREFIX: far) */
|
||||
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
|
||||
/** SOLID ICONS (PREFIX: fas (default)) */
|
||||
import {
|
||||
faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, faTag, faGlobe,
|
||||
faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { IVueBootstrapper, VueConstructor } from '../IVueBootstrapper';
|
||||
|
||||
export class IconBootstrapper implements IVueBootstrapper {
|
||||
public bootstrap(vue: VueConstructor): void {
|
||||
library.add(
|
||||
faGithub,
|
||||
faUserSecret,
|
||||
faSmile,
|
||||
faDesktop,
|
||||
faGlobe,
|
||||
faTag,
|
||||
faFolderOpen,
|
||||
faFolder,
|
||||
faTimes,
|
||||
faFileDownload,
|
||||
faSave,
|
||||
faCopy,
|
||||
faPlay,
|
||||
faSearch,
|
||||
faBatteryFull,
|
||||
faBatteryHalf,
|
||||
faInfoCircle,
|
||||
faArrowsAltH,
|
||||
);
|
||||
vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
}
|
||||
}
|
||||
@@ -4,30 +4,30 @@
|
||||
type="button"
|
||||
@click="onClicked"
|
||||
>
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="button__icon"
|
||||
:icon="[iconPrefix, iconName]"
|
||||
size="2x"
|
||||
:icon="iconName"
|
||||
/>
|
||||
<div class="button__text">{{text}}</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { IconName } from '@/presentation/components/Shared/Icon/IconName';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AppIcon,
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconPrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
type: String as PropType<IconName>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
@@ -64,6 +64,10 @@ export default defineComponent({
|
||||
box-shadow: 0 3px 9px $color-primary-darkest;
|
||||
border-radius: 4px;
|
||||
|
||||
&__icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
@include clickable;
|
||||
|
||||
width: 10%;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<span class="dollar">$</span>
|
||||
<code><slot /></code>
|
||||
<TooltipWrapper>
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="copy-button"
|
||||
:icon="['fas', 'copy']"
|
||||
icon="copy"
|
||||
@click="copyCode"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
@@ -19,10 +19,12 @@
|
||||
import { defineComponent, useSlots } from 'vue';
|
||||
import { Clipboard } from '@/infrastructure/Clipboard';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TooltipWrapper,
|
||||
AppIcon,
|
||||
},
|
||||
setup() {
|
||||
const slots = useSlots();
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<p>
|
||||
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
|
||||
steps. If you are unsure how to follow the instructions, hover on information
|
||||
(<font-awesome-icon :icon="['fas', 'info-circle']" />)
|
||||
(<AppIcon icon="circle-info" />)
|
||||
icons near the steps, or follow the easy alternative described above.
|
||||
</p>
|
||||
<p>
|
||||
@@ -27,9 +27,9 @@
|
||||
<div class="step__action">
|
||||
<span>{{ step.action.instruction }}</span>
|
||||
<TooltipWrapper v-if="step.action.details">
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
icon="circle-info"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
<div v-html="step.action.details" />
|
||||
@@ -39,9 +39,9 @@
|
||||
<div v-if="step.code" class="step__code">
|
||||
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
|
||||
<TooltipWrapper v-if="step.code.details">
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="explanation"
|
||||
:icon="['fas', 'info-circle']"
|
||||
icon="circle-info"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
<div v-html="step.code.details" />
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import CodeInstruction from './CodeInstruction.vue';
|
||||
import { IInstructionListData } from './InstructionListData';
|
||||
|
||||
@@ -69,6 +70,7 @@ export default defineComponent({
|
||||
components: {
|
||||
CodeInstruction,
|
||||
TooltipWrapper,
|
||||
AppIcon,
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
|
||||
@@ -4,19 +4,16 @@
|
||||
v-if="canRun"
|
||||
text="Run"
|
||||
v-on:click="executeCode"
|
||||
icon-prefix="fas"
|
||||
icon-name="play"
|
||||
/>
|
||||
<IconButton
|
||||
:text="isDesktopVersion ? 'Save' : 'Download'"
|
||||
v-on:click="saveCode"
|
||||
icon-prefix="fas"
|
||||
:icon-name="isDesktopVersion ? 'save' : 'file-download'"
|
||||
:icon-name="isDesktopVersion ? 'floppy-disk' : 'file-arrow-down'"
|
||||
/>
|
||||
<IconButton
|
||||
text="Copy"
|
||||
v-on:click="copyCode"
|
||||
icon-prefix="fas"
|
||||
icon-name="copy"
|
||||
/>
|
||||
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
:style="{ cursor: cursorCssValue }"
|
||||
@mousedown="startResize">
|
||||
<div class="line" />
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="icon"
|
||||
:icon="['fas', 'arrows-alt-h']"
|
||||
icon="left-right"
|
||||
/>
|
||||
<div class="line" />
|
||||
</div>
|
||||
@@ -14,8 +14,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, onUnmounted } from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AppIcon,
|
||||
},
|
||||
emits: {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
resized: (displacementX: number) => true,
|
||||
|
||||
@@ -17,18 +17,18 @@
|
||||
</span>
|
||||
<span v-else>Oh no 😢</span>
|
||||
<!-- Expand icon -->
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="card__inner__expand-icon"
|
||||
:icon="['far', isExpanded ? 'folder-open' : 'folder']"
|
||||
:icon="isExpanded ? 'folder-open' : 'folder'"
|
||||
/>
|
||||
<!-- Indeterminate and full states -->
|
||||
<div class="card__inner__state-icons">
|
||||
<font-awesome-icon
|
||||
:icon="['fa', 'battery-half']"
|
||||
<AppIcon
|
||||
icon="battery-half"
|
||||
v-if="isAnyChildSelected && !areAllChildrenSelected"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
:icon="['fa', 'battery-full']"
|
||||
<AppIcon
|
||||
icon="battery-full"
|
||||
v-if="areAllChildrenSelected"
|
||||
/>
|
||||
</div>
|
||||
@@ -38,8 +38,8 @@
|
||||
<ScriptsTree :categoryId="categoryId" />
|
||||
</div>
|
||||
<div class="card__expander__close-button">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
<AppIcon
|
||||
icon="xmark"
|
||||
v-on:click="collapse()"
|
||||
/>
|
||||
</div>
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
defineComponent, ref, watch, computed,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
|
||||
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||
@@ -59,6 +60,7 @@ import { sleep } from '@/infrastructure/Threading/AsyncSleep';
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ScriptsTree,
|
||||
AppIcon,
|
||||
},
|
||||
props: {
|
||||
categoryId: {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
class="search__query__close-button"
|
||||
v-on:click="clearSearchQuery()"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'times']" />
|
||||
<AppIcon icon="xmark" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!searchHasMatches" class="search-no-matches">
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
defineComponent, PropType, ref, computed,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
|
||||
import CardList from '@/presentation/components/Scripts/View/Cards/CardList.vue';
|
||||
@@ -52,6 +53,7 @@ export default defineComponent({
|
||||
components: {
|
||||
ScriptsTree,
|
||||
CardList,
|
||||
AppIcon,
|
||||
},
|
||||
props: {
|
||||
currentView: {
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
v-on:click.stop
|
||||
v-on:click="toggle()"
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'info-circle']" />
|
||||
<AppIcon icon="circle-info" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AppIcon,
|
||||
},
|
||||
emits: [
|
||||
'show',
|
||||
'hide',
|
||||
@@ -52,5 +56,4 @@ export default defineComponent({
|
||||
color: $color-primary-light;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
40
src/presentation/components/Shared/Icon/AppIcon.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div v-html="svgContent" class="inline-icon" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent,
|
||||
PropType,
|
||||
inject,
|
||||
} from 'vue';
|
||||
import { useSvgLoader } from './UseSvgLoader';
|
||||
import { IconName } from './IconName';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
icon: {
|
||||
type: String as PropType<IconName>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const useSvgLoaderHook = inject('useSvgLoaderHook', useSvgLoader);
|
||||
const { svgContent } = useSvgLoaderHook(() => props.icon);
|
||||
return { svgContent };
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.inline-icon {
|
||||
display: inline-block;
|
||||
::v-deep svg { // using ::v-deep because when v-html is used the content doesn't go through Vue's template compiler.
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
src/presentation/components/Shared/Icon/IconName.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export const IconNames = [
|
||||
'magnifying-glass',
|
||||
'copy',
|
||||
'circle-info',
|
||||
'user-secret',
|
||||
'tag',
|
||||
'github',
|
||||
'face-smile',
|
||||
'globe',
|
||||
'desktop',
|
||||
'xmark',
|
||||
'battery-half',
|
||||
'battery-full',
|
||||
'folder',
|
||||
'folder-open',
|
||||
'left-right',
|
||||
'file-arrow-down',
|
||||
'floppy-disk',
|
||||
'play',
|
||||
] as const;
|
||||
|
||||
export type IconName = typeof IconNames[number];
|
||||
92
src/presentation/components/Shared/Icon/UseSvgLoader.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
WatchSource, readonly, ref, watch,
|
||||
} from 'vue';
|
||||
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||
import { IconName } from './IconName';
|
||||
|
||||
export function useSvgLoader(
|
||||
iconWatcher: WatchSource<IconName>,
|
||||
loaders: FileLoaders = RawSvgLoaders,
|
||||
) {
|
||||
const svgContent = ref<string>('');
|
||||
|
||||
watch(iconWatcher, async (iconName) => {
|
||||
svgContent.value = await lazyLoadSvg(iconName, loaders);
|
||||
}, { immediate: true });
|
||||
|
||||
return {
|
||||
svgContent: readonly(svgContent),
|
||||
};
|
||||
}
|
||||
|
||||
export function clearIconCache() {
|
||||
LazyIconCache.clear();
|
||||
}
|
||||
|
||||
export type FileLoaders = Record<string, () => Promise<string>>;
|
||||
|
||||
const LazyIconCache = new Map<IconName, AsyncLazy<string>>();
|
||||
|
||||
async function lazyLoadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
|
||||
let iconLoader = LazyIconCache.get(name);
|
||||
if (!iconLoader) {
|
||||
iconLoader = new AsyncLazy<string>(() => loadSvg(name, loaders));
|
||||
LazyIconCache.set(name, iconLoader);
|
||||
}
|
||||
const icon = await iconLoader.getValue();
|
||||
return icon;
|
||||
}
|
||||
|
||||
async function loadSvg(name: IconName, loaders: FileLoaders): Promise<string> {
|
||||
const iconPath = `/assets/icons/${name}.svg`;
|
||||
const loader = loaders[iconPath];
|
||||
if (!loader) {
|
||||
throw new Error(`missing icon for "${name}" in "${iconPath}"`);
|
||||
}
|
||||
const svgContent = await loader();
|
||||
const modifiedContent = modifySvg(svgContent);
|
||||
return modifiedContent;
|
||||
}
|
||||
|
||||
const RawSvgLoaders = import.meta.glob('@/presentation/assets/icons/**/*.svg', {
|
||||
as: 'raw', // This will load the SVG file content as a string.
|
||||
/*
|
||||
Using `eager: true` to preload all icons.
|
||||
Pros:
|
||||
- Speed: Icons are instantly accessible post-initial load.
|
||||
Cons:
|
||||
- Increased initial load time due to preloading of all icons.
|
||||
- Increased bundle size.
|
||||
*/
|
||||
eager: false,
|
||||
});
|
||||
|
||||
function modifySvg(svgSource: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgSource, 'image/svg+xml');
|
||||
let svgRoot = doc.documentElement;
|
||||
svgRoot = removeSvgComments(svgRoot);
|
||||
svgRoot = fillSvgCurrentColor(svgRoot);
|
||||
return new XMLSerializer()
|
||||
.serializeToString(svgRoot);
|
||||
}
|
||||
|
||||
function removeSvgComments(svgRoot: HTMLElement): HTMLElement {
|
||||
const comments = Array.from(svgRoot.childNodes).filter(
|
||||
(node) => node.nodeType === Node.COMMENT_NODE,
|
||||
);
|
||||
for (const comment of comments) {
|
||||
svgRoot.removeChild(comment);
|
||||
}
|
||||
Array.from(svgRoot.children).forEach((child) => {
|
||||
removeSvgComments(child as HTMLElement);
|
||||
});
|
||||
return svgRoot;
|
||||
}
|
||||
|
||||
function fillSvgCurrentColor(svgRoot: HTMLElement): HTMLElement {
|
||||
svgRoot.querySelectorAll('path').forEach((el: Element) => {
|
||||
el.setAttribute('fill', 'currentColor');
|
||||
});
|
||||
return svgRoot;
|
||||
}
|
||||
@@ -10,9 +10,7 @@
|
||||
class="dialog__close-button"
|
||||
@click="hide"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'times']"
|
||||
/>
|
||||
<AppIcon icon="xmark" />
|
||||
</div>
|
||||
</div>
|
||||
</ModalContainer>
|
||||
@@ -20,11 +18,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import ModalContainer from './ModalContainer.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
ModalContainer,
|
||||
AppIcon,
|
||||
},
|
||||
emits: {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'container-supported': hasCurrentOsDesktopVersion,
|
||||
}">
|
||||
<span class="description">
|
||||
<font-awesome-icon class="description__icon" :icon="['fas', 'desktop']" />
|
||||
<AppIcon class="description__icon" icon="desktop" />
|
||||
<span class="description__text">For desktop:</span>
|
||||
</span>
|
||||
<span class="urls">
|
||||
@@ -21,6 +21,7 @@
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import DownloadUrlListItem from './DownloadUrlListItem.vue';
|
||||
|
||||
const supportedOperativeSystems: readonly OperatingSystem[] = [
|
||||
@@ -32,6 +33,7 @@ const supportedOperativeSystems: readonly OperatingSystem[] = [
|
||||
export default defineComponent({
|
||||
components: {
|
||||
DownloadUrlListItem,
|
||||
AppIcon,
|
||||
},
|
||||
setup() {
|
||||
const { os: currentOs } = inject(InjectionKeys.useRuntimeEnvironment);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="footer">
|
||||
<div class="footer__section">
|
||||
<span v-if="isDesktop" class="footer__section__item">
|
||||
<font-awesome-icon class="icon" :icon="['fas', 'globe']" />
|
||||
<AppIcon class="icon" icon="globe" />
|
||||
<span>
|
||||
Online version at <a :href="homepageUrl" target="_blank" rel="noopener noreferrer">{{ homepageUrl }}</a>
|
||||
</span>
|
||||
@@ -15,24 +15,24 @@
|
||||
<div class="footer__section">
|
||||
<div class="footer__section__item">
|
||||
<a :href="feedbackUrl" target="_blank" rel="noopener noreferrer">
|
||||
<font-awesome-icon class="icon" :icon="['far', 'smile']" />
|
||||
<AppIcon class="icon" icon="face-smile" />
|
||||
<span>Feedback</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer__section__item">
|
||||
<a :href="repositoryUrl" target="_blank" rel="noopener noreferrer">
|
||||
<font-awesome-icon class="icon" :icon="['fab', 'github']" />
|
||||
<AppIcon class="icon" icon="github" />
|
||||
<span>Source Code</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer__section__item">
|
||||
<a :href="releaseUrl" target="_blank" rel="noopener noreferrer">
|
||||
<font-awesome-icon class="icon" :icon="['fas', 'tag']" />
|
||||
<AppIcon class="icon" icon="tag" />
|
||||
<span>v{{ version }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="footer__section__item">
|
||||
<font-awesome-icon class="icon" :icon="['fas', 'user-secret']" />
|
||||
<AppIcon class="icon" icon="user-secret" />
|
||||
<a @click="showPrivacyDialog()">Privacy</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
defineComponent, ref, computed, inject,
|
||||
} from 'vue';
|
||||
import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import DownloadUrlList from './DownloadUrlList.vue';
|
||||
import PrivacyPolicy from './PrivacyPolicy.vue';
|
||||
@@ -57,6 +58,7 @@ export default defineComponent({
|
||||
ModalDialog,
|
||||
PrivacyPolicy,
|
||||
DownloadUrlList,
|
||||
AppIcon,
|
||||
},
|
||||
setup() {
|
||||
const { info } = inject(InjectionKeys.useApplication);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-model="searchQuery"
|
||||
>
|
||||
<div class="icon-wrapper">
|
||||
<font-awesome-icon :icon="['fas', 'search']" />
|
||||
<AppIcon icon="magnifying-glass" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,11 +19,13 @@ import {
|
||||
} from 'vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { NonCollapsing } from '@/presentation/components/Scripts/View/Cards/NonCollapsingDirective';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { IReadOnlyUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||
|
||||
export default defineComponent({
|
||||
components: { AppIcon },
|
||||
directives: {
|
||||
NonCollapsing,
|
||||
},
|
||||
|
||||