initialized
This commit is contained in:
parent
53b843daf5
commit
ec66700b1d
20
.browserslistrc
Normal file
20
.browserslistrc
Normal file
|
@ -0,0 +1,20 @@
|
|||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
17
.devcontainer/Dockerfile
Normal file
17
.devcontainer/Dockerfile
Normal file
|
@ -0,0 +1,17 @@
|
|||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/typescript-node/.devcontainer/base.Dockerfile
|
||||
|
||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 16, 14, 12, 16-bullseye, 14-bullseye, 12-bullseye, 16-buster, 14-buster, 12-buster
|
||||
ARG VARIANT="16-bullseye"
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
||||
|
||||
# [Optional] Uncomment this section to install additional OS packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
||||
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
||||
# ARG EXTRA_NODE_VERSION=10
|
||||
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
||||
|
||||
# [Optional] Uncomment if you want to install more global node packages
|
||||
# RUN su node -c "npm install -g <your-package-list -here>"
|
||||
RUN su node -c "npm install -g npm@8.12.1"
|
41
.devcontainer/devcontainer.json
Normal file
41
.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.224.2/containers/typescript-node
|
||||
{
|
||||
"name": "beteran-backend-app-browser",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
// Update 'VARIANT' to pick a Node version: 16, 14, 12.
|
||||
// Append -bullseye or -buster to pin to an OS version.
|
||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||
"args": {
|
||||
"VARIANT": "16-bullseye"
|
||||
}
|
||||
},
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"angular.ng-template",
|
||||
"donjayamanne.githistory",
|
||||
"eamodio.gitlens",
|
||||
"mhutchie.git-graph"
|
||||
],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
"portsAttributes": {
|
||||
"4300": {
|
||||
"label": "beteran-backend-app-browser",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
},
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "yarn install",
|
||||
"postCreateCommand": "bash ./.devcontainer/scripts/postCreateCommand.sh",
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "node"
|
||||
}
|
5
.devcontainer/scripts/postCreateCommand.sh
Normal file
5
.devcontainer/scripts/postCreateCommand.sh
Normal file
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
npm install -g @angular/cli
|
16
.editorconfig
Normal file
16
.editorconfig
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
79
.eslintrc.json
Normal file
79
.eslintrc.json
Normal file
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"ignorePatterns": ["projects/**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"parserOptions": {
|
||||
"project": ["tsconfig.json"],
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/ng-cli-compat",
|
||||
"plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
],
|
||||
"rules": {
|
||||
"@angular-eslint/component-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "element",
|
||||
"prefix": "",
|
||||
"style": "kebab-case"
|
||||
}
|
||||
],
|
||||
"@angular-eslint/directive-selector": [
|
||||
"error",
|
||||
{
|
||||
"type": "attribute",
|
||||
"prefix": "",
|
||||
"style": "camelCase"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/dot-notation": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"off",
|
||||
{
|
||||
"accessibility": "explicit"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
{ "leadingUnderscore": "allowSingleOrDouble" }
|
||||
],
|
||||
"@typescript-eslint/no-inferrable-types": "off",
|
||||
"arrow-parens": [
|
||||
"error",
|
||||
"as-needed",
|
||||
{
|
||||
"requireForBlockBody": true
|
||||
}
|
||||
],
|
||||
"brace-style": ["off", "off"],
|
||||
"import/order": "off",
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"ignorePattern": "^import |^export | implements",
|
||||
"code": 180
|
||||
}
|
||||
],
|
||||
"no-underscore-dangle": "off",
|
||||
"object-shorthand": "off",
|
||||
"quote-props": ["error", "consistent"],
|
||||
"quotes": ["error", "single"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"extends": ["plugin:@angular-eslint/template/recommended"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.angular/cache
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "pwa-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"
|
||||
}
|
||||
]
|
||||
}
|
32
.vscode/settings.json
vendored
Normal file
32
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnSave": true,
|
||||
"workbench.settings.useSplitJSON": true,
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/node_modules/*/**": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
42
.vscode/tasks.json
vendored
Normal file
42
.vscode/tasks.json
vendored
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
72
CREDITS
Normal file
72
CREDITS
Normal file
|
@ -0,0 +1,72 @@
|
|||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ 3rd party credits
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
// Flags
|
||||
https://github.com/Yummygum/flagpack-core
|
||||
|
||||
// Icons
|
||||
Material - https://material.io/tools/icons
|
||||
Feather - https://feathericons.com/
|
||||
Heroicons - https://github.com/refactoringui/heroicons
|
||||
Iconsmind - https://iconsmind.com/
|
||||
|
||||
// Avatars
|
||||
https://uifaces.co
|
||||
|
||||
// 404, 500 & Maintenance
|
||||
https://undraw.co
|
||||
|
||||
// Mail app
|
||||
Photo by Riccardo Chiarini on Unsplash - https://unsplash.com/photos/2VDa8bnLM8c
|
||||
Photo by Johannes Plenio on Unsplash - https://unsplash.com/photos/RwHv7LgeC7s
|
||||
Photo by Jamie Davies on Unsplash - https://unsplash.com/photos/Hao52Fu9-F8
|
||||
Photo by Christian Joudrey on Unsplash - https://unsplash.com/photos/mWRR1xj95hg
|
||||
|
||||
// Profile page
|
||||
Photo by Alex Knight on Unsplash - https://unsplash.com/photos/DpPutJwgyW8
|
||||
|
||||
// Cards
|
||||
Photo by Kym Ellis on Unsplash - https://unsplash.com/photos/RPT3AjdXlZc
|
||||
Photo by Patrick Hendry on Unsplash - https://unsplash.com/photos/Qgxk3PQsMiI
|
||||
Photo by Hailey Kean on Unsplash - https://unsplash.com/photos/QxjsOlFNr_4
|
||||
Photo by Nathan Anderson on Unsplash - https://unsplash.com/photos/mG8ShlWrMDI
|
||||
Photo by Adrian Infernus on Unsplash - https://unsplash.com/photos/5apewqWk978
|
||||
Photo by freestocks.org on Unsplash - https://unsplash.com/photos/c73TZ2sIU38
|
||||
Photo by Tim Marshall on Unsplash - https://unsplash.com/photos/PKSCrmZdvwA
|
||||
Photo by Daniel Koponyas on Unsplash - https://unsplash.com/photos/rbiLY6ZwvXQ
|
||||
Photo by John Westrock on Unsplash - https://unsplash.com/photos/LCesauDseu8
|
||||
Photo by Gabriel Sollmann on Unsplash - https://unsplash.com/photos/kFWj9y-tJB4
|
||||
Photo by Kevin Wolf on Unsplash - https://unsplash.com/photos/BJyjgEdNTPs
|
||||
Photo by Luca Bravo on Unsplash - https://unsplash.com/photos/hFzIoD0F_i8
|
||||
Photo by Ian Baldwin on Unsplash - https://unsplash.com/photos/Dlj-SxxTlQ0
|
||||
Photo by Ben Kolde on Unsplash - https://unsplash.com/photos/KRTFIBOfcFw
|
||||
Photo by Chad Peltola on Unsplash - https://unsplash.com/photos/BTvQ2ET_iKc
|
||||
Photo by rocknwool on Unsplash - https://unsplash.com/photos/r56oO1V5oms
|
||||
Photo by Vita Vilcina on Unsplash - https://unsplash.com/photos/KtOid0FLjqU
|
||||
Photo by Jia Ye on Unsplash - https://unsplash.com/photos/y8ZnQqgohLk
|
||||
Photo by Parker Whitson on Unsplash - https://unsplash.com/photos/OlTYIqTjmVM
|
||||
Photo by Dorian Hurst on Unsplash - https://unsplash.com/photos/a9uWPQlIbYc
|
||||
Photo by Everaldo Coelho on Unsplash - https://unsplash.com/photos/KPaSCpklCZw
|
||||
Photo by eberhard grossgasteiger on Unsplash - https://unsplash.com/photos/fh2JefbNlII
|
||||
Photo by Orlova Maria on Unsplash - https://unsplash.com/photos/p8y4dWEMGMU
|
||||
Photo by Jake Blucker on Unsplash - https://unsplash.com/photos/tMzCrBkM99Y
|
||||
Photo by Jerry Zhang on Unsplash - https://unsplash.com/photos/oIBcow6n36s
|
||||
Photo by John Cobb on Unsplash - https://unsplash.com/photos/IE_sifhay7o
|
||||
Photo by Dan Gold on Unsplash - https://unsplash.com/photos/mDlhOIfGxNI
|
||||
Photo by Ana Toma on Unsplash - https://unsplash.com/photos/XsGwe6gYg0c
|
||||
Photo by Andrea on Unsplash - https://unsplash.com/photos/1AWY0N960Sk
|
||||
Photo by Aswin on Unsplash - https://unsplash.com/photos/_roUcFWstas
|
||||
Photo by Justin Kauffman on Unsplash - https://unsplash.com/photos/aWG_dqyhI0A
|
||||
Photo by Barna Bartis on Unsplash - https://unsplash.com/photos/VVoBQqWrvkc
|
||||
Photo by Kyle Hinkson on Unsplash - https://unsplash.com/photos/3439EnvnAGo
|
||||
Photo by Spencer Watson on Unsplash - https://unsplash.com/photos/5TBf16GnHKg
|
||||
Photo by adrian on Unsplash - https://unsplash.com/photos/1wrzvwoK8A4
|
||||
Photo by Christopher Rusev on Unsplash - https://unsplash.com/photos/7gKWgCRixf0
|
||||
Photo by Stephen Leonardi on Unsplash - https://unsplash.com/photos/MDmwQVgDHHM
|
||||
Photo by Dwinanda Nurhanif Mujito on Unsplash - https://unsplash.com/photos/pKT5Mg16w_w
|
||||
Photo by Humphrey Muleba on Unsplash - https://unsplash.com/photos/Zuvf5mxT5fs
|
||||
Photo by adrian on Unsplash - https://unsplash.com/photos/PNRxLFPMyJY
|
||||
Photo by Dahee Son on Unsplash - https://unsplash.com/photos/tV06QVJXVxU
|
||||
Photo by Zachary Kyra-Derksen on Unsplash - https://unsplash.com/photos/vkqS7vLQUtg
|
||||
Photo by Rodrigo Soares on Unsplash - https://unsplash.com/photos/8BFWBUkSqQo
|
6
LICENSE.md
Normal file
6
LICENSE.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
Envato Standard License
|
||||
|
||||
Copyright (c) Sercan Yemen <sercanyemen@gmail.com>
|
||||
|
||||
This project is protected by Envato's Standard License. For more information,
|
||||
check the official license page at [https://themeforest.net/licenses/standard](https://themeforest.net/licenses/standard)
|
93
README.md
93
README.md
|
@ -1,92 +1,27 @@
|
|||
# beteran-backend-app-browser
|
||||
# Fuse - Admin template and Starter project for Angular
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli)
|
||||
|
||||
## Development server
|
||||
|
||||
## Getting started
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
## Code scaffolding
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Add your files
|
||||
## Build
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.loafle.net/bet/beteran-backend-app-browser.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
## Running unit tests
|
||||
|
||||
## Integrate with your tools
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.loafle.net/bet/beteran-backend-app-browser/-/settings/integrations)
|
||||
## Running end-to-end tests
|
||||
|
||||
## Collaborate with your team
|
||||
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
## Further help
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||
|
|
135
angular.json
Normal file
135
angular.json
Normal file
|
@ -0,0 +1,135 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"defaultCollection": "@angular-eslint/schematics"
|
||||
},
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"fuse": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/fuse",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"allowedCommonJsDependencies": [
|
||||
"apexcharts",
|
||||
"highlight.js",
|
||||
"crypto-js"
|
||||
],
|
||||
"assets": [
|
||||
"src/favicon-16x16.png",
|
||||
"src/favicon-32x32.png",
|
||||
"src/assets",
|
||||
{
|
||||
"glob": "_redirects",
|
||||
"input": "src",
|
||||
"output": "/"
|
||||
}
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": ["src/@fuse/styles"]
|
||||
},
|
||||
"styles": [
|
||||
"src/@fuse/styles/tailwind.scss",
|
||||
"src/@fuse/styles/themes.scss",
|
||||
"src/styles/vendors.scss",
|
||||
"src/@fuse/styles/main.scss",
|
||||
"src/styles/styles.scss",
|
||||
"src/styles/tailwind.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "3mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "75kb",
|
||||
"maximumError": "90kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "fuse:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "fuse:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "fuse:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon-16x16.png",
|
||||
"src/favicon-32x32.png",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": ["src/styles/styles.scss"],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-eslint/builder:lint",
|
||||
"options": {
|
||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "fuse"
|
||||
}
|
41
karma.conf.js
Normal file
41
karma.conf.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: "",
|
||||
frameworks: ["jasmine", "@angular-devkit/build-angular"],
|
||||
plugins: [
|
||||
require("karma-jasmine"),
|
||||
require("karma-chrome-launcher"),
|
||||
require("karma-jasmine-html-reporter"),
|
||||
require("karma-coverage"),
|
||||
require("@angular-devkit/build-angular/plugins/karma"),
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true, // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require("path").join(__dirname, "./coverage/fuse"),
|
||||
subdir: ".",
|
||||
reporters: [{ type: "html" }, { type: "text-summary" }],
|
||||
},
|
||||
reporters: ["progress", "kjhtml"],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ["Chrome"],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true,
|
||||
});
|
||||
};
|
81
package.json
Normal file
81
package.json
Normal file
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"name": "@fuse/starter",
|
||||
"version": "14.2.0",
|
||||
"description": "Fuse - Angular Admin Template and Starter Project",
|
||||
"author": "https://themeforest.net/user/srcn",
|
||||
"license": "https://themeforest.net/licenses/standard",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "13.2.3",
|
||||
"@angular/cdk": "13.2.3",
|
||||
"@angular/common": "13.2.3",
|
||||
"@angular/compiler": "13.2.3",
|
||||
"@angular/core": "13.2.3",
|
||||
"@angular/forms": "13.2.3",
|
||||
"@angular/material": "13.2.3",
|
||||
"@angular/material-moment-adapter": "13.2.3",
|
||||
"@angular/platform-browser": "13.2.3",
|
||||
"@angular/platform-browser-dynamic": "13.2.3",
|
||||
"@angular/router": "13.2.3",
|
||||
"@ngneat/transloco": "3.1.4",
|
||||
"apexcharts": "3.33.1",
|
||||
"crypto-js": "3.3.0",
|
||||
"highlight.js": "11.4.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"ng-apexcharts": "1.7.0",
|
||||
"ngx-markdown": "13.1.0",
|
||||
"ngx-quill": "16.1.2",
|
||||
"perfect-scrollbar": "1.5.3",
|
||||
"quill": "1.3.7",
|
||||
"rxjs": "7.5.4",
|
||||
"tslib": "2.3.1",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "13.2.4",
|
||||
"@angular-eslint/builder": "13.1.0",
|
||||
"@angular-eslint/eslint-plugin": "13.1.0",
|
||||
"@angular-eslint/eslint-plugin-template": "13.1.0",
|
||||
"@angular-eslint/schematics": "13.1.0",
|
||||
"@angular-eslint/template-parser": "13.1.0",
|
||||
"@angular/cli": "13.2.4",
|
||||
"@angular/compiler-cli": "13.2.3",
|
||||
"@tailwindcss/aspect-ratio": "0.4.0",
|
||||
"@tailwindcss/line-clamp": "0.3.1",
|
||||
"@tailwindcss/typography": "0.5.2",
|
||||
"@types/chroma-js": "2.1.3",
|
||||
"@types/crypto-js": "3.1.47",
|
||||
"@types/highlight.js": "10.1.0",
|
||||
"@types/jasmine": "3.10.3",
|
||||
"@types/lodash": "4.14.178",
|
||||
"@types/lodash-es": "4.17.6",
|
||||
"@types/node": "12.20.46",
|
||||
"@typescript-eslint/eslint-plugin": "5.12.0",
|
||||
"@typescript-eslint/parser": "5.12.0",
|
||||
"autoprefixer": "10.4.2",
|
||||
"chroma-js": "2.4.2",
|
||||
"eslint": "8.9.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
"eslint-plugin-jsdoc": "37.9.4",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
"jasmine-core": "4.0.0",
|
||||
"karma": "6.3.16",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage": "2.1.1",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-jasmine-html-reporter": "1.7.0",
|
||||
"lodash": "4.17.21",
|
||||
"postcss": "8.4.6",
|
||||
"tailwindcss": "3.0.23",
|
||||
"typescript": "4.5.5"
|
||||
}
|
||||
}
|
12
src/@fuse/animations/defaults.ts
Normal file
12
src/@fuse/animations/defaults.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export class FuseAnimationCurves {
|
||||
static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)';
|
||||
static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)';
|
||||
static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)';
|
||||
static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)';
|
||||
}
|
||||
|
||||
export class FuseAnimationDurations {
|
||||
static complex = '375ms';
|
||||
static entering = '225ms';
|
||||
static exiting = '195ms';
|
||||
}
|
34
src/@fuse/animations/expand-collapse.ts
Normal file
34
src/@fuse/animations/expand-collapse.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Expand / collapse
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const expandCollapse = trigger('expandCollapse',
|
||||
[
|
||||
state('void, collapsed',
|
||||
style({
|
||||
height: '0'
|
||||
})
|
||||
),
|
||||
|
||||
state('*, expanded',
|
||||
style('*')
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void <=> false, collapsed <=> false, expanded <=> false', []),
|
||||
|
||||
// Transition
|
||||
transition('void <=> *, collapsed <=> expanded',
|
||||
animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export { expandCollapse };
|
330
src/@fuse/animations/fade.ts
Normal file
330
src/@fuse/animations/fade.ts
Normal file
|
@ -0,0 +1,330 @@
|
|||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade in
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeIn = trigger('fadeIn',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
opacity: 0
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity: 1
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade in top
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeInTop = trigger('fadeInTop',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(0, -100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade in bottom
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeInBottom = trigger('fadeInBottom',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(0, 100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade in left
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeInLeft = trigger('fadeInLeft',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(-100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade in right
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeInRight = trigger('fadeInRight',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade out
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeOut = trigger('fadeOut',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
opacity: 1
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity: 0
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade out top
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeOutTop = trigger('fadeOutTop',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(0, -100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade out bottom
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeOutBottom = trigger('fadeOutBottom',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(0, 100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade out left
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeOutLeft = trigger('fadeOutLeft',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(-100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Fade out right
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const fadeOutRight = trigger('fadeOutRight',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'translate3d(100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export { fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight, fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight };
|
1
src/@fuse/animations/index.ts
Normal file
1
src/@fuse/animations/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/animations/public-api';
|
15
src/@fuse/animations/public-api.ts
Normal file
15
src/@fuse/animations/public-api.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { expandCollapse } from '@fuse/animations/expand-collapse';
|
||||
import { fadeIn, fadeInBottom, fadeInLeft, fadeInRight, fadeInTop, fadeOut, fadeOutBottom, fadeOutLeft, fadeOutRight, fadeOutTop } from '@fuse/animations/fade';
|
||||
import { shake } from '@fuse/animations/shake';
|
||||
import { slideInBottom, slideInLeft, slideInRight, slideInTop, slideOutBottom, slideOutLeft, slideOutRight, slideOutTop } from '@fuse/animations/slide';
|
||||
import { zoomIn, zoomOut } from '@fuse/animations/zoom';
|
||||
|
||||
export const fuseAnimations = [
|
||||
expandCollapse,
|
||||
fadeIn, fadeInTop, fadeInBottom, fadeInLeft, fadeInRight,
|
||||
fadeOut, fadeOutTop, fadeOutBottom, fadeOutLeft, fadeOutRight,
|
||||
shake,
|
||||
slideInTop, slideInBottom, slideInLeft, slideInRight,
|
||||
slideOutTop, slideOutBottom, slideOutLeft, slideOutRight,
|
||||
zoomIn, zoomOut
|
||||
];
|
73
src/@fuse/animations/shake.ts
Normal file
73
src/@fuse/animations/shake.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { animate, keyframes, style, transition, trigger } from '@angular/animations';
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Shake
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const shake = trigger('shake',
|
||||
[
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *, * => true',
|
||||
[
|
||||
animate('{{timings}}',
|
||||
keyframes([
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
offset : 0
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(-10px, 0, 0)',
|
||||
offset : 0.1
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(10px, 0, 0)',
|
||||
offset : 0.2
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(-10px, 0, 0)',
|
||||
offset : 0.3
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(10px, 0, 0)',
|
||||
offset : 0.4
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(-10px, 0, 0)',
|
||||
offset : 0.5
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(10px, 0, 0)',
|
||||
offset : 0.6
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(-10px, 0, 0)',
|
||||
offset : 0.7
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(10px, 0, 0)',
|
||||
offset : 0.8
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(-10px, 0, 0)',
|
||||
offset : 0.9
|
||||
}),
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)',
|
||||
offset : 1
|
||||
})
|
||||
])
|
||||
)
|
||||
],
|
||||
{
|
||||
params: {
|
||||
timings: '0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955)'
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export { shake };
|
252
src/@fuse/animations/slide.ts
Normal file
252
src/@fuse/animations/slide.ts
Normal file
|
@ -0,0 +1,252 @@
|
|||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide in top
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideInTop = trigger('slideInTop',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(0, -100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide in bottom
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideInBottom = trigger('slideInBottom',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(0, 100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide in left
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideInLeft = trigger('slideInLeft',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(-100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide in right
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideInRight = trigger('slideInRight',
|
||||
[
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide out top
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideOutTop = trigger('slideOutTop',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(0, -100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide out bottom
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideOutBottom = trigger('slideOutBottom',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(0, 100%, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide out left
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideOutLeft = trigger('slideOutLeft',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(-100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Slide out right
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const slideOutRight = trigger('slideOutRight',
|
||||
[
|
||||
state('*',
|
||||
style({
|
||||
transform: 'translate3d(0, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
transform: 'translate3d(100%, 0, 0)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export { slideInTop, slideInBottom, slideInLeft, slideInRight, slideOutTop, slideOutBottom, slideOutLeft, slideOutRight };
|
73
src/@fuse/animations/zoom.ts
Normal file
73
src/@fuse/animations/zoom.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { FuseAnimationCurves, FuseAnimationDurations } from '@fuse/animations/defaults';
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Zoom in
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const zoomIn = trigger('zoomIn',
|
||||
[
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'scale(0.5)'
|
||||
})
|
||||
),
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'scale(1)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('void => false', []),
|
||||
|
||||
// Transition
|
||||
transition('void => *', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.entering} ${FuseAnimationCurves.deceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Zoom out
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
const zoomOut = trigger('zoomOut',
|
||||
[
|
||||
|
||||
state('*',
|
||||
style({
|
||||
opacity : 1,
|
||||
transform: 'scale(1)'
|
||||
})
|
||||
),
|
||||
|
||||
state('void',
|
||||
style({
|
||||
opacity : 0,
|
||||
transform: 'scale(0.5)'
|
||||
})
|
||||
),
|
||||
|
||||
// Prevent the transition if the state is false
|
||||
transition('false => void', []),
|
||||
|
||||
// Transition
|
||||
transition('* => void', animate('{{timings}}'),
|
||||
{
|
||||
params: {
|
||||
timings: `${FuseAnimationDurations.exiting} ${FuseAnimationCurves.acceleration}`
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
export { zoomIn, zoomOut };
|
||||
|
76
src/@fuse/components/alert/alert.component.html
Normal file
76
src/@fuse/components/alert/alert.component.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<div
|
||||
class="fuse-alert-container"
|
||||
*ngIf="!dismissible || (dismissible && !dismissed)"
|
||||
[@fadeIn]="!dismissed"
|
||||
[@fadeOut]="!dismissed"
|
||||
>
|
||||
<!-- Border -->
|
||||
<div class="fuse-alert-border" *ngIf="appearance === 'border'"></div>
|
||||
|
||||
<!-- Icon -->
|
||||
<div class="fuse-alert-icon" *ngIf="showIcon">
|
||||
<!-- Custom icon -->
|
||||
<div class="fuse-alert-custom-icon">
|
||||
<ng-content select="[fuseAlertIcon]"></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Default icons -->
|
||||
<div class="fuse-alert-default-icon">
|
||||
<mat-icon
|
||||
*ngIf="type === 'primary'"
|
||||
[svgIcon]="'heroicons_solid:check-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'accent'"
|
||||
[svgIcon]="'heroicons_solid:check-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'warn'"
|
||||
[svgIcon]="'heroicons_solid:x-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'basic'"
|
||||
[svgIcon]="'heroicons_solid:check-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'info'"
|
||||
[svgIcon]="'heroicons_solid:information-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'success'"
|
||||
[svgIcon]="'heroicons_solid:check-circle'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'warning'"
|
||||
[svgIcon]="'heroicons_solid:exclamation'"
|
||||
></mat-icon>
|
||||
|
||||
<mat-icon
|
||||
*ngIf="type === 'error'"
|
||||
[svgIcon]="'heroicons_solid:x-circle'"
|
||||
></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="fuse-alert-content">
|
||||
<div class="fuse-alert-title">
|
||||
<ng-content select="[fuseAlertTitle]"></ng-content>
|
||||
</div>
|
||||
|
||||
<div class="fuse-alert-message">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dismiss button -->
|
||||
<button class="fuse-alert-dismiss-button" mat-icon-button (click)="dismiss()">
|
||||
<mat-icon [svgIcon]="'heroicons_solid:x'"></mat-icon>
|
||||
</button>
|
||||
</div>
|
1340
src/@fuse/components/alert/alert.component.scss
Normal file
1340
src/@fuse/components/alert/alert.component.scss
Normal file
File diff suppressed because it is too large
Load Diff
213
src/@fuse/components/alert/alert.component.ts
Normal file
213
src/@fuse/components/alert/alert.component.ts
Normal file
|
@ -0,0 +1,213 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { filter, Subject, takeUntil } from 'rxjs';
|
||||
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { fuseAnimations } from '@fuse/animations';
|
||||
import {
|
||||
FuseAlertAppearance,
|
||||
FuseAlertType,
|
||||
} from '@fuse/components/alert/alert.types';
|
||||
import { FuseAlertService } from '@fuse/components/alert/alert.service';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-alert',
|
||||
templateUrl: './alert.component.html',
|
||||
styleUrls: ['./alert.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: fuseAnimations,
|
||||
exportAs: 'fuseAlert',
|
||||
})
|
||||
export class FuseAlertComponent implements OnChanges, OnInit, OnDestroy {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_dismissible: BooleanInput;
|
||||
static ngAcceptInputType_dismissed: BooleanInput;
|
||||
static ngAcceptInputType_showIcon: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() appearance: FuseAlertAppearance = 'soft';
|
||||
@Input() dismissed: boolean = false;
|
||||
@Input() dismissible: boolean = false;
|
||||
@Input() name: string = this._fuseUtilsService.randomId();
|
||||
@Input() showIcon: boolean = true;
|
||||
@Input() type: FuseAlertType = 'primary';
|
||||
@Output() readonly dismissedChanged: EventEmitter<boolean> =
|
||||
new EventEmitter<boolean>();
|
||||
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseAlertService: FuseAlertService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Host binding for component classes
|
||||
*/
|
||||
@HostBinding('class') get classList(): any {
|
||||
return {
|
||||
'fuse-alert-appearance-border': this.appearance === 'border',
|
||||
'fuse-alert-appearance-fill': this.appearance === 'fill',
|
||||
'fuse-alert-appearance-outline': this.appearance === 'outline',
|
||||
'fuse-alert-appearance-soft': this.appearance === 'soft',
|
||||
'fuse-alert-dismissed': this.dismissed,
|
||||
'fuse-alert-dismissible': this.dismissible,
|
||||
'fuse-alert-show-icon': this.showIcon,
|
||||
'fuse-alert-type-primary': this.type === 'primary',
|
||||
'fuse-alert-type-accent': this.type === 'accent',
|
||||
'fuse-alert-type-warn': this.type === 'warn',
|
||||
'fuse-alert-type-basic': this.type === 'basic',
|
||||
'fuse-alert-type-info': this.type === 'info',
|
||||
'fuse-alert-type-success': this.type === 'success',
|
||||
'fuse-alert-type-warning': this.type === 'warning',
|
||||
'fuse-alert-type-error': this.type === 'error',
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Dismissed
|
||||
if ('dismissed' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.dismissed = coerceBooleanProperty(changes['dismissed'].currentValue);
|
||||
|
||||
// Dismiss/show the alert
|
||||
this._toggleDismiss(this.dismissed);
|
||||
}
|
||||
|
||||
// Dismissible
|
||||
if ('dismissible' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.dismissible = coerceBooleanProperty(
|
||||
changes['dismissed'].currentValue
|
||||
);
|
||||
}
|
||||
|
||||
// Show icon
|
||||
if ('showIcon' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.showIcon = coerceBooleanProperty(changes['showIcon'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Subscribe to the dismiss calls
|
||||
this._fuseAlertService.onDismiss
|
||||
.pipe(
|
||||
filter((name: any) => this.name === name),
|
||||
takeUntil(this._unsubscribeAll)
|
||||
)
|
||||
.subscribe(() => {
|
||||
// Dismiss the alert
|
||||
this.dismiss();
|
||||
});
|
||||
|
||||
// Subscribe to the show calls
|
||||
this._fuseAlertService.onShow
|
||||
.pipe(
|
||||
filter((name: any) => this.name === name),
|
||||
takeUntil(this._unsubscribeAll)
|
||||
)
|
||||
.subscribe(() => {
|
||||
// Show the alert
|
||||
this.show();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Dismiss the alert
|
||||
*/
|
||||
dismiss(): void {
|
||||
// Return if the alert is already dismissed
|
||||
if (this.dismissed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dismiss the alert
|
||||
this._toggleDismiss(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dismissed alert
|
||||
*/
|
||||
show(): void {
|
||||
// Return if the alert is already showing
|
||||
if (!this.dismissed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show the alert
|
||||
this._toggleDismiss(false);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Dismiss/show the alert
|
||||
*
|
||||
* @param dismissed
|
||||
* @private
|
||||
*/
|
||||
private _toggleDismiss(dismissed: boolean): void {
|
||||
// Return if the alert is not dismissible
|
||||
if (!this.dismissible) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the dismissed
|
||||
this.dismissed = dismissed;
|
||||
|
||||
// Execute the observable
|
||||
this.dismissedChanged.next(this.dismissed);
|
||||
|
||||
// Notify the change detector
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
22
src/@fuse/components/alert/alert.module.ts
Normal file
22
src/@fuse/components/alert/alert.module.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { FuseAlertComponent } from '@fuse/components/alert/alert.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseAlertComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
MatIconModule
|
||||
],
|
||||
exports : [
|
||||
FuseAlertComponent
|
||||
]
|
||||
})
|
||||
export class FuseAlertModule
|
||||
{
|
||||
}
|
77
src/@fuse/components/alert/alert.service.ts
Normal file
77
src/@fuse/components/alert/alert.service.ts
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Observable, ReplaySubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FuseAlertService
|
||||
{
|
||||
private readonly _onDismiss: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
private readonly _onShow: ReplaySubject<string> = new ReplaySubject<string>(1);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Getter for onDismiss
|
||||
*/
|
||||
get onDismiss(): Observable<any>
|
||||
{
|
||||
return this._onDismiss.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for onShow
|
||||
*/
|
||||
get onShow(): Observable<any>
|
||||
{
|
||||
return this._onShow.asObservable();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Dismiss the alert
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
dismiss(name: string): void
|
||||
{
|
||||
// Return if the name is not provided
|
||||
if ( !name )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the observable
|
||||
this._onDismiss.next(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dismissed alert
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
show(name: string): void
|
||||
{
|
||||
// Return if the name is not provided
|
||||
if ( !name )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute the observable
|
||||
this._onShow.next(name);
|
||||
}
|
||||
|
||||
}
|
15
src/@fuse/components/alert/alert.types.ts
Normal file
15
src/@fuse/components/alert/alert.types.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export type FuseAlertAppearance =
|
||||
| 'border'
|
||||
| 'fill'
|
||||
| 'outline'
|
||||
| 'soft';
|
||||
|
||||
export type FuseAlertType =
|
||||
| 'primary'
|
||||
| 'accent'
|
||||
| 'warn'
|
||||
| 'basic'
|
||||
| 'info'
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'error';
|
1
src/@fuse/components/alert/index.ts
Normal file
1
src/@fuse/components/alert/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/alert/public-api';
|
4
src/@fuse/components/alert/public-api.ts
Normal file
4
src/@fuse/components/alert/public-api.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from '@fuse/components/alert/alert.component';
|
||||
export * from '@fuse/components/alert/alert.module';
|
||||
export * from '@fuse/components/alert/alert.service';
|
||||
export * from '@fuse/components/alert/alert.types';
|
23
src/@fuse/components/card/card.component.html
Normal file
23
src/@fuse/components/card/card.component.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!-- Flippable card -->
|
||||
<ng-container *ngIf="flippable">
|
||||
<!-- Front -->
|
||||
<div class="fuse-card-front">
|
||||
<ng-content select="[fuseCardFront]"></ng-content>
|
||||
</div>
|
||||
|
||||
<!-- Back -->
|
||||
<div class="fuse-card-back">
|
||||
<ng-content select="[fuseCardBack]"></ng-content>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Normal card -->
|
||||
<ng-container *ngIf="!flippable">
|
||||
<!-- Content -->
|
||||
<ng-content></ng-content>
|
||||
|
||||
<!-- Expansion -->
|
||||
<div class="fuse-card-expansion" *ngIf="expanded" [@expandCollapse]>
|
||||
<ng-content select="[fuseCardExpansion]"></ng-content>
|
||||
</div>
|
||||
</ng-container>
|
63
src/@fuse/components/card/card.component.scss
Normal file
63
src/@fuse/components/card/card.component.scss
Normal file
|
@ -0,0 +1,63 @@
|
|||
fuse-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
@apply rounded-2xl shadow bg-card;
|
||||
|
||||
/* Flippable */
|
||||
&.fuse-card-flippable {
|
||||
border-radius: 0;
|
||||
overflow: visible;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 1s;
|
||||
perspective: 600px;
|
||||
background: transparent;
|
||||
@apply shadow-none;
|
||||
|
||||
&.fuse-card-face-back {
|
||||
|
||||
.fuse-card-front {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
.fuse-card-back {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transform: rotateY(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-card-front,
|
||||
.fuse-card-back {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
z-index: 10;
|
||||
transition: transform 0.5s ease-out 0s, visibility 0s ease-in 0.2s, opacity 0s ease-in 0.2s;
|
||||
backface-visibility: hidden;
|
||||
@apply rounded-2xl shadow bg-card;
|
||||
}
|
||||
|
||||
.fuse-card-front {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: rotateY(0deg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fuse-card-back {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: rotateY(180deg);
|
||||
overflow: hidden auto;
|
||||
}
|
||||
}
|
||||
}
|
74
src/@fuse/components/card/card.component.ts
Normal file
74
src/@fuse/components/card/card.component.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
Component,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { fuseAnimations } from '@fuse/animations';
|
||||
import { FuseCardFace } from '@fuse/components/card/card.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-card',
|
||||
templateUrl: './card.component.html',
|
||||
styleUrls: ['./card.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: fuseAnimations,
|
||||
exportAs: 'fuseCard',
|
||||
})
|
||||
export class FuseCardComponent implements OnChanges {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_expanded: BooleanInput;
|
||||
static ngAcceptInputType_flippable: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() expanded: boolean = false;
|
||||
@Input() face: FuseCardFace = 'front';
|
||||
@Input() flippable: boolean = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Host binding for component classes
|
||||
*/
|
||||
@HostBinding('class') get classList(): any {
|
||||
return {
|
||||
'fuse-card-expanded': this.expanded,
|
||||
'fuse-card-face-back': this.flippable && this.face === 'back',
|
||||
'fuse-card-face-front': this.flippable && this.face === 'front',
|
||||
'fuse-card-flippable': this.flippable,
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Expanded
|
||||
if ('expanded' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.expanded = coerceBooleanProperty(changes['expanded'].currentValue);
|
||||
}
|
||||
|
||||
// Flippable
|
||||
if ('flippable' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.flippable = coerceBooleanProperty(changes['flippable'].currentValue);
|
||||
}
|
||||
}
|
||||
}
|
18
src/@fuse/components/card/card.module.ts
Normal file
18
src/@fuse/components/card/card.module.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FuseCardComponent } from '@fuse/components/card/card.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseCardComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule
|
||||
],
|
||||
exports : [
|
||||
FuseCardComponent
|
||||
]
|
||||
})
|
||||
export class FuseCardModule
|
||||
{
|
||||
}
|
3
src/@fuse/components/card/card.types.ts
Normal file
3
src/@fuse/components/card/card.types.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export type FuseCardFace =
|
||||
| 'front'
|
||||
| 'back';
|
1
src/@fuse/components/card/index.ts
Normal file
1
src/@fuse/components/card/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/card/public-api';
|
2
src/@fuse/components/card/public-api.ts
Normal file
2
src/@fuse/components/card/public-api.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from '@fuse/components/card/card.component';
|
||||
export * from '@fuse/components/card/card.module';
|
3
src/@fuse/components/drawer/drawer.component.html
Normal file
3
src/@fuse/components/drawer/drawer.component.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="fuse-drawer-content">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
133
src/@fuse/components/drawer/drawer.component.scss
Normal file
133
src/@fuse/components/drawer/drawer.component.scss
Normal file
|
@ -0,0 +1,133 @@
|
|||
/* Variables */
|
||||
:root {
|
||||
--fuse-drawer-width: 320px;
|
||||
}
|
||||
|
||||
fuse-drawer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
width: var(--fuse-drawer-width);
|
||||
min-width: var(--fuse-drawer-width);
|
||||
max-width: var(--fuse-drawer-width);
|
||||
z-index: 300;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .35);
|
||||
@apply bg-card;
|
||||
|
||||
/* Animations */
|
||||
&.fuse-drawer-animations-enabled {
|
||||
transition-duration: 400ms;
|
||||
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
transition-property: visibility, margin-left, margin-right, transform, width, max-width, min-width;
|
||||
|
||||
.fuse-drawer-content {
|
||||
transition-duration: 400ms;
|
||||
transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
transition-property: width, max-width, min-width;
|
||||
}
|
||||
}
|
||||
|
||||
/* Over mode */
|
||||
&.fuse-drawer-mode-over {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
/* Fixed mode */
|
||||
&.fuse-drawer-fixed {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
/* Left position */
|
||||
&.fuse-drawer-position-left {
|
||||
|
||||
/* Side mode */
|
||||
&.fuse-drawer-mode-side {
|
||||
margin-left: calc(var(--fuse-drawer-width) * -1);
|
||||
|
||||
&.fuse-drawer-opened {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Over mode */
|
||||
&.fuse-drawer-mode-over {
|
||||
left: 0;
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
|
||||
&.fuse-drawer-opened {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.fuse-drawer-content {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Right position */
|
||||
&.fuse-drawer-position-right {
|
||||
|
||||
/* Side mode */
|
||||
&.fuse-drawer-mode-side {
|
||||
margin-right: calc(var(--fuse-drawer-width) * -1);
|
||||
|
||||
&.fuse-drawer-opened {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Over mode */
|
||||
&.fuse-drawer-mode-over {
|
||||
right: 0;
|
||||
transform: translate3d(100%, 0, 0);
|
||||
|
||||
&.fuse-drawer-opened {
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.fuse-drawer-content {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.fuse-drawer-content {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
@apply bg-card;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overlay */
|
||||
.fuse-drawer-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 299;
|
||||
opacity: 1;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
|
||||
/* Fixed mode */
|
||||
&.fuse-drawer-overlay-fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* Transparent overlay */
|
||||
&.fuse-drawer-overlay-transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
434
src/@fuse/components/drawer/drawer.component.ts
Normal file
434
src/@fuse/components/drawer/drawer.component.ts
Normal file
|
@ -0,0 +1,434 @@
|
|||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
Renderer2,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
animate,
|
||||
AnimationBuilder,
|
||||
AnimationPlayer,
|
||||
style,
|
||||
} from '@angular/animations';
|
||||
import {
|
||||
FuseDrawerMode,
|
||||
FuseDrawerPosition,
|
||||
} from '@fuse/components/drawer/drawer.types';
|
||||
import { FuseDrawerService } from '@fuse/components/drawer/drawer.service';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-drawer',
|
||||
templateUrl: './drawer.component.html',
|
||||
styleUrls: ['./drawer.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'fuseDrawer',
|
||||
})
|
||||
export class FuseDrawerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_fixed: BooleanInput;
|
||||
static ngAcceptInputType_opened: BooleanInput;
|
||||
static ngAcceptInputType_transparentOverlay: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() fixed: boolean = false;
|
||||
@Input() mode: FuseDrawerMode = 'side';
|
||||
@Input() name: string = this._fuseUtilsService.randomId();
|
||||
@Input() opened: boolean = false;
|
||||
@Input() position: FuseDrawerPosition = 'left';
|
||||
@Input() transparentOverlay: boolean = false;
|
||||
@Output() readonly fixedChanged: EventEmitter<boolean> =
|
||||
new EventEmitter<boolean>();
|
||||
@Output() readonly modeChanged: EventEmitter<FuseDrawerMode> =
|
||||
new EventEmitter<FuseDrawerMode>();
|
||||
@Output() readonly openedChanged: EventEmitter<boolean> =
|
||||
new EventEmitter<boolean>();
|
||||
@Output() readonly positionChanged: EventEmitter<FuseDrawerPosition> =
|
||||
new EventEmitter<FuseDrawerPosition>();
|
||||
|
||||
private _animationsEnabled: boolean = false;
|
||||
private _hovered: boolean = false;
|
||||
private _overlay?: HTMLElement;
|
||||
private _player?: AnimationPlayer;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _animationBuilder: AnimationBuilder,
|
||||
private _elementRef: ElementRef,
|
||||
private _renderer2: Renderer2,
|
||||
private _fuseDrawerService: FuseDrawerService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Host binding for component classes
|
||||
*/
|
||||
@HostBinding('class') get classList(): any {
|
||||
return {
|
||||
'fuse-drawer-animations-enabled': this._animationsEnabled,
|
||||
'fuse-drawer-fixed': this.fixed,
|
||||
'fuse-drawer-hover': this._hovered,
|
||||
[`fuse-drawer-mode-${this.mode}`]: true,
|
||||
'fuse-drawer-opened': this.opened,
|
||||
[`fuse-drawer-position-${this.position}`]: true,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Host binding for component inline styles
|
||||
*/
|
||||
@HostBinding('style') get styleList(): any {
|
||||
return {
|
||||
visibility: this.opened ? 'visible' : 'hidden',
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Decorated methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On mouseenter
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
@HostListener('mouseenter')
|
||||
private _onMouseenter(): void {
|
||||
// Enable the animations
|
||||
this._enableAnimations();
|
||||
|
||||
// Set the hovered
|
||||
this._hovered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* On mouseleave
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
@HostListener('mouseleave')
|
||||
private _onMouseleave(): void {
|
||||
// Enable the animations
|
||||
this._enableAnimations();
|
||||
|
||||
// Set the hovered
|
||||
this._hovered = false;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Fixed
|
||||
if ('fixed' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.fixed = coerceBooleanProperty(changes['fixed'].currentValue);
|
||||
|
||||
// Execute the observable
|
||||
this.fixedChanged.next(this.fixed);
|
||||
}
|
||||
|
||||
// Mode
|
||||
if ('mode' in changes) {
|
||||
// Get the previous and current values
|
||||
const previousMode = changes['mode'].previousValue;
|
||||
const currentMode = changes['mode'].currentValue;
|
||||
|
||||
// Disable the animations
|
||||
this._disableAnimations();
|
||||
|
||||
// If the mode changes: 'over -> side'
|
||||
if (previousMode === 'over' && currentMode === 'side') {
|
||||
// Hide the overlay
|
||||
this._hideOverlay();
|
||||
}
|
||||
|
||||
// If the mode changes: 'side -> over'
|
||||
if (previousMode === 'side' && currentMode === 'over') {
|
||||
// If the drawer is opened
|
||||
if (this.opened) {
|
||||
// Show the overlay
|
||||
this._showOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the observable
|
||||
this.modeChanged.next(currentMode);
|
||||
|
||||
// Enable the animations after a delay
|
||||
// The delay must be bigger than the current transition-duration
|
||||
// to make sure nothing will be animated while the mode is changing
|
||||
setTimeout(() => {
|
||||
this._enableAnimations();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Opened
|
||||
if ('opened' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
const open = coerceBooleanProperty(changes['opened'].currentValue);
|
||||
|
||||
// Open/close the drawer
|
||||
this._toggleOpened(open);
|
||||
}
|
||||
|
||||
// Position
|
||||
if ('position' in changes) {
|
||||
// Execute the observable
|
||||
this.positionChanged.next(this.position);
|
||||
}
|
||||
|
||||
// Transparent overlay
|
||||
if ('transparentOverlay' in changes) {
|
||||
// Coerce the value to a boolean
|
||||
this.transparentOverlay = coerceBooleanProperty(
|
||||
changes['transparentOverlay'].currentValue
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Register the drawer
|
||||
this._fuseDrawerService.registerComponent(this.name, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Finish the animation
|
||||
if (this._player) {
|
||||
this._player.finish();
|
||||
}
|
||||
|
||||
// Deregister the drawer from the registry
|
||||
this._fuseDrawerService.deregisterComponent(this.name);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Open the drawer
|
||||
*/
|
||||
open(): void {
|
||||
// Return if the drawer has already opened
|
||||
if (this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the drawer
|
||||
this._toggleOpened(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the drawer
|
||||
*/
|
||||
close(): void {
|
||||
// Return if the drawer has already closed
|
||||
if (!this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the drawer
|
||||
this._toggleOpened(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the drawer
|
||||
*/
|
||||
toggle(): void {
|
||||
if (this.opened) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Enable the animations
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _enableAnimations(): void {
|
||||
// Return if the animations are already enabled
|
||||
if (this._animationsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enable the animations
|
||||
this._animationsEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the animations
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _disableAnimations(): void {
|
||||
// Return if the animations are already disabled
|
||||
if (!this._animationsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable the animations
|
||||
this._animationsEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the backdrop
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _showOverlay(): void {
|
||||
// Create the backdrop element
|
||||
this._overlay = this._renderer2.createElement('div');
|
||||
|
||||
// Return if overlay couldn't be create for some reason
|
||||
if (!this._overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a class to the backdrop element
|
||||
this._overlay.classList.add('fuse-drawer-overlay');
|
||||
|
||||
// Add a class depending on the fixed option
|
||||
if (this.fixed) {
|
||||
this._overlay.classList.add('fuse-drawer-overlay-fixed');
|
||||
}
|
||||
|
||||
// Add a class depending on the transparentOverlay option
|
||||
if (this.transparentOverlay) {
|
||||
this._overlay.classList.add('fuse-drawer-overlay-transparent');
|
||||
}
|
||||
|
||||
// Append the backdrop to the parent of the drawer
|
||||
this._renderer2.appendChild(
|
||||
this._elementRef.nativeElement.parentElement,
|
||||
this._overlay
|
||||
);
|
||||
|
||||
// Create the enter animation and attach it to the player
|
||||
this._player = this._animationBuilder
|
||||
.build([
|
||||
style({ opacity: 0 }),
|
||||
animate(
|
||||
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
|
||||
style({ opacity: 1 })
|
||||
),
|
||||
])
|
||||
.create(this._overlay);
|
||||
|
||||
// Once the animation is done...
|
||||
this._player.onDone(() => {
|
||||
// Destroy the player
|
||||
this._player?.destroy();
|
||||
this._player = undefined;
|
||||
});
|
||||
|
||||
// Play the animation
|
||||
this._player.play();
|
||||
|
||||
// Add an event listener to the overlay
|
||||
this._overlay.addEventListener('click', () => {
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the backdrop
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _hideOverlay(): void {
|
||||
if (!this._overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the leave animation and attach it to the player
|
||||
this._player = this._animationBuilder
|
||||
.build([
|
||||
animate(
|
||||
'300ms cubic-bezier(0.25, 0.8, 0.25, 1)',
|
||||
style({ opacity: 0 })
|
||||
),
|
||||
])
|
||||
.create(this._overlay);
|
||||
|
||||
// Play the animation
|
||||
this._player.play();
|
||||
|
||||
// Once the animation is done...
|
||||
this._player.onDone(() => {
|
||||
// Destroy the player
|
||||
this._player?.destroy();
|
||||
this._player = undefined;
|
||||
|
||||
// If the backdrop still exists...
|
||||
if (this._overlay) {
|
||||
// Remove the backdrop
|
||||
this._overlay.parentNode?.removeChild(this._overlay);
|
||||
this._overlay = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open/close the drawer
|
||||
*
|
||||
* @param open
|
||||
* @private
|
||||
*/
|
||||
private _toggleOpened(open: boolean): void {
|
||||
// Set the opened
|
||||
this.opened = open;
|
||||
|
||||
// Enable the animations
|
||||
this._enableAnimations();
|
||||
|
||||
// If the mode is 'over'
|
||||
if (this.mode === 'over') {
|
||||
// If the drawer opens, show the overlay
|
||||
if (open) {
|
||||
this._showOverlay();
|
||||
}
|
||||
// Otherwise, close the overlay
|
||||
else {
|
||||
this._hideOverlay();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the observable
|
||||
this.openedChanged.next(open);
|
||||
}
|
||||
}
|
18
src/@fuse/components/drawer/drawer.module.ts
Normal file
18
src/@fuse/components/drawer/drawer.module.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseDrawerComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule
|
||||
],
|
||||
exports : [
|
||||
FuseDrawerComponent
|
||||
]
|
||||
})
|
||||
export class FuseDrawerModule
|
||||
{
|
||||
}
|
52
src/@fuse/components/drawer/drawer.service.ts
Normal file
52
src/@fuse/components/drawer/drawer.service.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { FuseDrawerComponent } from '@fuse/components/drawer/drawer.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FuseDrawerService
|
||||
{
|
||||
private _componentRegistry: Map<string, FuseDrawerComponent> = new Map<string, FuseDrawerComponent>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register drawer component
|
||||
*
|
||||
* @param name
|
||||
* @param component
|
||||
*/
|
||||
registerComponent(name: string, component: FuseDrawerComponent): void
|
||||
{
|
||||
this._componentRegistry.set(name, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregister drawer component
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
deregisterComponent(name: string): void
|
||||
{
|
||||
this._componentRegistry.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get drawer component from the registry
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
getComponent(name: string): FuseDrawerComponent | undefined
|
||||
{
|
||||
return this._componentRegistry.get(name);
|
||||
}
|
||||
}
|
7
src/@fuse/components/drawer/drawer.types.ts
Normal file
7
src/@fuse/components/drawer/drawer.types.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
export type FuseDrawerMode =
|
||||
| 'over'
|
||||
| 'side';
|
||||
|
||||
export type FuseDrawerPosition =
|
||||
| 'left'
|
||||
| 'right';
|
1
src/@fuse/components/drawer/index.ts
Normal file
1
src/@fuse/components/drawer/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/drawer/public-api';
|
4
src/@fuse/components/drawer/public-api.ts
Normal file
4
src/@fuse/components/drawer/public-api.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from '@fuse/components/drawer/drawer.component';
|
||||
export * from '@fuse/components/drawer/drawer.module';
|
||||
export * from '@fuse/components/drawer/drawer.service';
|
||||
export * from '@fuse/components/drawer/drawer.types';
|
13
src/@fuse/components/fullscreen/fullscreen.component.html
Normal file
13
src/@fuse/components/fullscreen/fullscreen.component.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<!-- Button -->
|
||||
<button
|
||||
mat-icon-button
|
||||
[matTooltip]="tooltip || 'Toggle Fullscreen'"
|
||||
(click)="toggleFullscreen()"
|
||||
>
|
||||
<ng-container [ngTemplateOutlet]="iconTpl || defaultIconTpl"></ng-container>
|
||||
</button>
|
||||
|
||||
<!-- Default icon -->
|
||||
<ng-template #defaultIconTpl>
|
||||
<mat-icon [svgIcon]="'heroicons_outline:arrows-expand'"></mat-icon>
|
||||
</ng-template>
|
155
src/@fuse/components/fullscreen/fullscreen.component.ts
Normal file
155
src/@fuse/components/fullscreen/fullscreen.component.ts
Normal file
|
@ -0,0 +1,155 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
Input,
|
||||
OnInit,
|
||||
TemplateRef,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import {
|
||||
FSDocument,
|
||||
FSDocumentElement,
|
||||
} from '@fuse/components/fullscreen/fullscreen.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-fullscreen',
|
||||
templateUrl: './fullscreen.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
exportAs: 'fuseFullscreen',
|
||||
})
|
||||
export class FuseFullscreenComponent implements OnInit {
|
||||
@Input() iconTpl?: TemplateRef<any>;
|
||||
@Input() tooltip?: string;
|
||||
private _fsDoc: FSDocument;
|
||||
private _fsDocEl?: FSDocumentElement;
|
||||
private _isFullscreen: boolean = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(@Inject(DOCUMENT) private _document: Document) {
|
||||
this._fsDoc = _document as FSDocument;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this._fsDocEl = document.documentElement as FSDocumentElement;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Toggle the fullscreen mode
|
||||
*/
|
||||
toggleFullscreen(): void {
|
||||
// Check if the fullscreen is open
|
||||
this._isFullscreen = this._getBrowserFullscreenElement() !== null;
|
||||
|
||||
// Toggle the fullscreen
|
||||
if (this._isFullscreen) {
|
||||
this._closeFullscreen();
|
||||
} else {
|
||||
this._openFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get browser's fullscreen element
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _getBrowserFullscreenElement(): Element | null {
|
||||
if (typeof this._fsDoc.fullscreenElement !== 'undefined') {
|
||||
return this._fsDoc.fullscreenElement;
|
||||
}
|
||||
|
||||
if (typeof this._fsDoc.mozFullScreenElement !== 'undefined') {
|
||||
return this._fsDoc.mozFullScreenElement;
|
||||
}
|
||||
|
||||
if (typeof this._fsDoc.msFullscreenElement !== 'undefined') {
|
||||
return this._fsDoc.msFullscreenElement;
|
||||
}
|
||||
|
||||
if (typeof this._fsDoc.webkitFullscreenElement !== 'undefined') {
|
||||
return this._fsDoc.webkitFullscreenElement;
|
||||
}
|
||||
|
||||
throw new Error('Fullscreen mode is not supported by this browser');
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the fullscreen
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _openFullscreen(): void {
|
||||
if (this._fsDocEl?.requestFullscreen) {
|
||||
this._fsDocEl.requestFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Firefox
|
||||
if (this._fsDocEl?.mozRequestFullScreen) {
|
||||
this._fsDocEl.mozRequestFullScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Chrome, Safari and Opera
|
||||
if (this._fsDocEl?.webkitRequestFullscreen) {
|
||||
this._fsDocEl.webkitRequestFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// IE/Edge
|
||||
if (this._fsDocEl?.msRequestFullscreen) {
|
||||
this._fsDocEl.msRequestFullscreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the fullscreen
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _closeFullscreen(): void {
|
||||
if (this._fsDoc.exitFullscreen) {
|
||||
this._fsDoc.exitFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Firefox
|
||||
if (this._fsDoc.mozCancelFullScreen) {
|
||||
this._fsDoc.mozCancelFullScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Chrome, Safari and Opera
|
||||
if (this._fsDoc.webkitExitFullscreen) {
|
||||
this._fsDoc.webkitExitFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// IE/Edge
|
||||
else if (this._fsDoc.msExitFullscreen) {
|
||||
this._fsDoc.msExitFullscreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
24
src/@fuse/components/fullscreen/fullscreen.module.ts
Normal file
24
src/@fuse/components/fullscreen/fullscreen.module.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { FuseFullscreenComponent } from '@fuse/components/fullscreen/fullscreen.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseFullscreenComponent
|
||||
],
|
||||
imports : [
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
CommonModule
|
||||
],
|
||||
exports : [
|
||||
FuseFullscreenComponent
|
||||
]
|
||||
})
|
||||
export class FuseFullscreenModule
|
||||
{
|
||||
}
|
16
src/@fuse/components/fullscreen/fullscreen.types.ts
Normal file
16
src/@fuse/components/fullscreen/fullscreen.types.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export interface FSDocument extends HTMLDocument
|
||||
{
|
||||
mozFullScreenElement?: Element;
|
||||
mozCancelFullScreen?: () => void;
|
||||
msFullscreenElement?: Element;
|
||||
msExitFullscreen?: () => void;
|
||||
webkitFullscreenElement?: Element;
|
||||
webkitExitFullscreen?: () => void;
|
||||
}
|
||||
|
||||
export interface FSDocumentElement extends HTMLElement
|
||||
{
|
||||
mozRequestFullScreen?: () => void;
|
||||
msRequestFullscreen?: () => void;
|
||||
webkitRequestFullscreen?: () => void;
|
||||
}
|
1
src/@fuse/components/fullscreen/index.ts
Normal file
1
src/@fuse/components/fullscreen/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/fullscreen/public-api';
|
3
src/@fuse/components/fullscreen/public-api.ts
Normal file
3
src/@fuse/components/fullscreen/public-api.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from '@fuse/components/fullscreen/fullscreen.component';
|
||||
export * from '@fuse/components/fullscreen/fullscreen.module';
|
||||
export * from '@fuse/components/fullscreen/fullscreen.types';
|
11
src/@fuse/components/highlight/highlight.component.html
Normal file
11
src/@fuse/components/highlight/highlight.component.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<ng-content></ng-content>
|
||||
|
||||
<!-- @formatter:off -->
|
||||
<ng-template let-highlightedCode="highlightedCode" let-lang="lang">
|
||||
<div class="fuse-highlight fuse-highlight-code-container">
|
||||
<pre
|
||||
[ngClass]="'language-' + lang"
|
||||
><code [ngClass]="'language-' + lang" [innerHTML]="highlightedCode"></code></pre>
|
||||
</div>
|
||||
</ng-template>
|
||||
<!-- @formatter:on -->
|
3
src/@fuse/components/highlight/highlight.component.scss
Normal file
3
src/@fuse/components/highlight/highlight.component.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
textarea[fuse-highlight] {
|
||||
display: none;
|
||||
}
|
140
src/@fuse/components/highlight/highlight.component.ts
Normal file
140
src/@fuse/components/highlight/highlight.component.ts
Normal file
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
EmbeddedViewRef,
|
||||
Input,
|
||||
OnChanges,
|
||||
Renderer2,
|
||||
SecurityContext,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { FuseHighlightService } from '@fuse/components/highlight/highlight.service';
|
||||
|
||||
@Component({
|
||||
selector: 'textarea[fuse-highlight]',
|
||||
templateUrl: './highlight.component.html',
|
||||
styleUrls: ['./highlight.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
exportAs: 'fuseHighlight',
|
||||
})
|
||||
export class FuseHighlightComponent implements OnChanges, AfterViewInit {
|
||||
@Input() code?: string;
|
||||
@Input() lang?: string;
|
||||
@ViewChild(TemplateRef) templateRef!: TemplateRef<any>;
|
||||
|
||||
highlightedCode: string | null = null;
|
||||
private _viewRef?: EmbeddedViewRef<any>;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _domSanitizer: DomSanitizer,
|
||||
private _elementRef: ElementRef,
|
||||
private _renderer2: Renderer2,
|
||||
private _fuseHighlightService: FuseHighlightService,
|
||||
private _viewContainerRef: ViewContainerRef
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Code & Lang
|
||||
if ('code' in changes || 'lang' in changes) {
|
||||
// Return if the viewContainerRef is not available
|
||||
if (!this._viewContainerRef.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Highlight and insert the code
|
||||
this._highlightAndInsert();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
// Return if there is no language set
|
||||
if (!this.lang) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there is no code input, get the code from
|
||||
// the textarea
|
||||
if (!this.code) {
|
||||
// Get the code
|
||||
this.code = this._elementRef.nativeElement.value;
|
||||
}
|
||||
|
||||
// Highlight and insert
|
||||
this._highlightAndInsert();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Highlight and insert the highlighted code
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _highlightAndInsert(): void {
|
||||
// Return if the template reference is not available
|
||||
if (!this.templateRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if the code or language is not defined
|
||||
if (!this.code || !this.lang) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the component if there is already one
|
||||
if (this._viewRef) {
|
||||
this._viewRef.destroy();
|
||||
this._viewRef = undefined;
|
||||
}
|
||||
|
||||
// Highlight and sanitize the code just in case
|
||||
this.highlightedCode = this._domSanitizer.sanitize(
|
||||
SecurityContext.HTML,
|
||||
this._fuseHighlightService.highlight(this.code, this.lang)
|
||||
);
|
||||
|
||||
// Return if the highlighted code is null
|
||||
if (this.highlightedCode === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render and insert the template
|
||||
this._viewRef = this._viewContainerRef.createEmbeddedView(
|
||||
this.templateRef,
|
||||
{
|
||||
highlightedCode: this.highlightedCode,
|
||||
lang: this.lang,
|
||||
}
|
||||
);
|
||||
|
||||
// Detect the changes
|
||||
this._viewRef.detectChanges();
|
||||
}
|
||||
}
|
18
src/@fuse/components/highlight/highlight.module.ts
Normal file
18
src/@fuse/components/highlight/highlight.module.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FuseHighlightComponent } from '@fuse/components/highlight/highlight.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseHighlightComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule
|
||||
],
|
||||
exports : [
|
||||
FuseHighlightComponent
|
||||
]
|
||||
})
|
||||
export class FuseHighlightModule
|
||||
{
|
||||
}
|
82
src/@fuse/components/highlight/highlight.service.ts
Normal file
82
src/@fuse/components/highlight/highlight.service.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FuseHighlightService
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor()
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Highlight
|
||||
*/
|
||||
highlight(code: string, language: string): string
|
||||
{
|
||||
// Format the code
|
||||
code = this._format(code);
|
||||
|
||||
// Highlight and return the code
|
||||
return hljs.highlight(code, {language}).value;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Remove the empty lines around the code block
|
||||
* and re-align the indentation based on the first
|
||||
* non-whitespace indented character
|
||||
*
|
||||
* @param code
|
||||
* @private
|
||||
*/
|
||||
private _format(code: string): string
|
||||
{
|
||||
let indentation = 0;
|
||||
|
||||
// Split the code into lines and store the lines
|
||||
const lines = code.split('\n');
|
||||
|
||||
// Trim the empty lines around the code block
|
||||
while ( lines.length && lines[0].trim() === '' )
|
||||
{
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
while ( lines.length && lines[lines.length - 1].trim() === '' )
|
||||
{
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
// Iterate through the lines
|
||||
lines.filter(line => line.length)
|
||||
.forEach((line, index) => {
|
||||
|
||||
// Always get the indentation of the first line so we can
|
||||
// have something to compare with
|
||||
if ( index === 0 )
|
||||
{
|
||||
indentation = line.search(/\S|$/);
|
||||
return;
|
||||
}
|
||||
|
||||
// Look at all the remaining lines to figure out the smallest indentation.
|
||||
indentation = Math.min(line.search(/\S|$/), indentation);
|
||||
});
|
||||
|
||||
// Iterate through the lines one more time, remove the extra
|
||||
// indentation, join them together and return it
|
||||
return lines.map(line => line.substring(indentation)).join('\n');
|
||||
}
|
||||
}
|
1
src/@fuse/components/highlight/index.ts
Normal file
1
src/@fuse/components/highlight/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/highlight/public-api';
|
3
src/@fuse/components/highlight/public-api.ts
Normal file
3
src/@fuse/components/highlight/public-api.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from '@fuse/components/highlight/highlight.component';
|
||||
export * from '@fuse/components/highlight/highlight.module';
|
||||
export * from '@fuse/components/highlight/highlight.service';
|
1
src/@fuse/components/loading-bar/index.ts
Normal file
1
src/@fuse/components/loading-bar/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/loading-bar/public-api';
|
|
@ -0,0 +1,3 @@
|
|||
<ng-container *ngIf="show && !!mode">
|
||||
<mat-progress-bar [mode]="mode" [value]="progress"></mat-progress-bar>
|
||||
</ng-container>
|
|
@ -0,0 +1,7 @@
|
|||
fuse-loading-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
}
|
84
src/@fuse/components/loading-bar/loading-bar.component.ts
Normal file
84
src/@fuse/components/loading-bar/loading-bar.component.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseLoadingService } from '@fuse/services/loading';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-loading-bar',
|
||||
templateUrl: './loading-bar.component.html',
|
||||
styleUrls: ['./loading-bar.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'fuseLoadingBar',
|
||||
})
|
||||
export class FuseLoadingBarComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input() autoMode: boolean = true;
|
||||
mode?: 'determinate' | 'indeterminate';
|
||||
progress: number = 0;
|
||||
show: boolean = false;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseLoadingService: FuseLoadingService) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Auto mode
|
||||
if ('autoMode' in changes) {
|
||||
// Set the auto mode in the service
|
||||
this._fuseLoadingService.setAutoMode(
|
||||
coerceBooleanProperty(changes['autoMode'].currentValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Subscribe to the service
|
||||
this._fuseLoadingService.mode$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((value) => {
|
||||
this.mode = value;
|
||||
});
|
||||
|
||||
this._fuseLoadingService.progress$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((value) => {
|
||||
this.progress = value;
|
||||
});
|
||||
|
||||
this._fuseLoadingService.show$
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((value) => {
|
||||
this.show = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
20
src/@fuse/components/loading-bar/loading-bar.module.ts
Normal file
20
src/@fuse/components/loading-bar/loading-bar.module.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { FuseLoadingBarComponent } from '@fuse/components/loading-bar/loading-bar.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseLoadingBarComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule,
|
||||
MatProgressBarModule
|
||||
],
|
||||
exports : [
|
||||
FuseLoadingBarComponent
|
||||
]
|
||||
})
|
||||
export class FuseLoadingBarModule
|
||||
{
|
||||
}
|
2
src/@fuse/components/loading-bar/public-api.ts
Normal file
2
src/@fuse/components/loading-bar/public-api.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from '@fuse/components/loading-bar/loading-bar.component';
|
||||
export * from '@fuse/components/loading-bar/loading-bar.module';
|
1
src/@fuse/components/masonry/index.ts
Normal file
1
src/@fuse/components/masonry/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/masonry/public-api';
|
3
src/@fuse/components/masonry/masonry.component.html
Normal file
3
src/@fuse/components/masonry/masonry.component.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="flex">
|
||||
<ng-container *ngTemplateOutlet="columnsTemplate; context: { $implicit: distributedColumns }"></ng-container>
|
||||
</div>
|
86
src/@fuse/components/masonry/masonry.component.ts
Normal file
86
src/@fuse/components/masonry/masonry.component.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { AfterViewInit, Component, Input, OnChanges, SimpleChanges, TemplateRef, ViewEncapsulation } from '@angular/core';
|
||||
import { fuseAnimations } from '@fuse/animations';
|
||||
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
|
||||
|
||||
@Component({
|
||||
selector : 'fuse-masonry',
|
||||
templateUrl : './masonry.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations : fuseAnimations,
|
||||
exportAs : 'fuseMasonry'
|
||||
})
|
||||
export class FuseMasonryComponent implements OnChanges, AfterViewInit
|
||||
{
|
||||
@Input() columnsTemplate: TemplateRef<any>;
|
||||
@Input() columns: number;
|
||||
@Input() items: any[] = [];
|
||||
distributedColumns: any[] = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(private _fuseMediaWatcherService: FuseMediaWatcherService)
|
||||
{
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void
|
||||
{
|
||||
// Columns
|
||||
if ( 'columns' in changes )
|
||||
{
|
||||
// Distribute the items
|
||||
this._distributeItems();
|
||||
}
|
||||
|
||||
// Items
|
||||
if ( 'items' in changes )
|
||||
{
|
||||
// Distribute the items
|
||||
this._distributeItems();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After view init
|
||||
*/
|
||||
ngAfterViewInit(): void
|
||||
{
|
||||
// Distribute the items for the first time
|
||||
this._distributeItems();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Distribute items into columns
|
||||
*/
|
||||
private _distributeItems(): void
|
||||
{
|
||||
// Return an empty array if there are no items
|
||||
if ( this.items.length === 0 )
|
||||
{
|
||||
this.distributedColumns = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the distributed columns array
|
||||
this.distributedColumns = Array.from(Array(this.columns), item => ({items: []}));
|
||||
|
||||
// Distribute the items to columns
|
||||
for ( let i = 0; i < this.items.length; i++ )
|
||||
{
|
||||
this.distributedColumns[i % this.columns].items.push(this.items[i]);
|
||||
}
|
||||
}
|
||||
}
|
18
src/@fuse/components/masonry/masonry.module.ts
Normal file
18
src/@fuse/components/masonry/masonry.module.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FuseMasonryComponent } from '@fuse/components/masonry/masonry.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseMasonryComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule
|
||||
],
|
||||
exports : [
|
||||
FuseMasonryComponent
|
||||
]
|
||||
})
|
||||
export class FuseMasonryModule
|
||||
{
|
||||
}
|
2
src/@fuse/components/masonry/public-api.ts
Normal file
2
src/@fuse/components/masonry/public-api.ts
Normal file
|
@ -0,0 +1,2 @@
|
|||
export * from '@fuse/components/masonry/masonry.component';
|
||||
export * from '@fuse/components/masonry/masonry.module';
|
|
@ -0,0 +1,177 @@
|
|||
<!-- Item wrapper -->
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-wrapper"
|
||||
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item?.subtitle"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<!-- Item with an internal link -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && !item?.externalLink && !item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[routerLink]="[item?.link]"
|
||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an external link -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && item?.externalLink && !item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[href]="item?.link"
|
||||
[target]="item?.target || '_self'"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with a function -->
|
||||
<ng-container *ngIf="!item?.link && item?.function && !item?.disabled">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an internal link and function -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && !item?.externalLink && item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[routerLink]="[item?.link]"
|
||||
[routerLinkActive]="'fuse-horizontal-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an external link and function -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && item?.externalLink && item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[href]="item?.link"
|
||||
[target]="item?.target || '_self'"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
mat-menu-item
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with a no link and no function -->
|
||||
<ng-container *ngIf="!item?.link && !item?.function && !item?.disabled">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item is disabled -->
|
||||
<ng-container *ngIf="item?.disabled">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item fuse-horizontal-navigation-item-disabled"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Item template -->
|
||||
<ng-template #itemTemplate>
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item?.icon">
|
||||
<mat-icon
|
||||
class="fuse-horizontal-navigation-item-icon"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.icon ? item.classes.icon : ''
|
||||
"
|
||||
[svgIcon]="!!item && !!item.icon ? item.icon : ''"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-horizontal-navigation-item-title-wrapper">
|
||||
<div class="fuse-horizontal-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title ? item.classes.title : ''
|
||||
"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item?.subtitle">
|
||||
<div class="fuse-horizontal-navigation-item-subtitle text-hint">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.subtitle
|
||||
? item.classes.subtitle
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item?.badge">
|
||||
<div class="fuse-horizontal-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-badge-content"
|
||||
[ngClass]="
|
||||
!!item && !!item.badge && !!item.badge.classes
|
||||
? item.badge.classes
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.badge?.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -0,0 +1,86 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-horizontal-navigation-basic-item',
|
||||
templateUrl: './basic.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseHorizontalNavigationBasicItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
isActiveMatchOptions: IsActiveMatchOptions;
|
||||
private _fuseHorizontalNavigationComponent?: FuseHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
) {
|
||||
// Set the equivalent of {exact: false} as default for active match options.
|
||||
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
// will throw an error and stop working.
|
||||
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Set the "isActiveMatchOptions" either from item's
|
||||
// "isActiveMatchOptions" or the equivalent form of
|
||||
// item's "exactMatch" option
|
||||
this.isActiveMatchOptions =
|
||||
this.item?.isActiveMatchOptions ?? this.item?.exactMatch
|
||||
? this._fuseUtilsService.exactMatchOptions
|
||||
: this._fuseUtilsService.subsetMatchOptions;
|
||||
|
||||
// Get the parent navigation component
|
||||
this._fuseHorizontalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseHorizontalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<ng-container *ngIf="!child">
|
||||
<div
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-menu-active': trigger.menuOpen,
|
||||
'fuse-horizontal-navigation-menu-active-forced': item?.active
|
||||
}"
|
||||
[matMenuTriggerFor]="matMenu"
|
||||
(onMenuOpen)="triggerChangeDetection()"
|
||||
(onMenuClose)="triggerChangeDetection()"
|
||||
#trigger="matMenuTrigger"
|
||||
>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="itemTemplate; context: { $implicit: item }"
|
||||
></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<mat-menu
|
||||
class="fuse-horizontal-navigation-menu-panel"
|
||||
[overlapTrigger]="false"
|
||||
#matMenu="matMenu"
|
||||
>
|
||||
<ng-container *ngFor="let item of item?.children; trackBy: trackByFn">
|
||||
<!-- Skip the hidden items -->
|
||||
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
|
||||
<!-- Basic -->
|
||||
<ng-container *ngIf="item.type === 'basic'">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-menu-item"
|
||||
[disabled]="item.disabled"
|
||||
mat-menu-item
|
||||
>
|
||||
<fuse-horizontal-navigation-basic-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-horizontal-navigation-basic-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Branch: aside, collapsable, group -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item.type === 'aside' ||
|
||||
item.type === 'collapsable' ||
|
||||
item.type === 'group'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fuse-horizontal-navigation-menu-item"
|
||||
[disabled]="item.disabled"
|
||||
[matMenuTriggerFor]="branch.matMenu"
|
||||
mat-menu-item
|
||||
>
|
||||
<ng-container
|
||||
*ngTemplateOutlet="itemTemplate; context: { $implicit: item }"
|
||||
></ng-container>
|
||||
<fuse-horizontal-navigation-branch-item
|
||||
[child]="true"
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
#branch
|
||||
></fuse-horizontal-navigation-branch-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Divider -->
|
||||
<ng-container *ngIf="item.type === 'divider'">
|
||||
<div class="fuse-horizontal-navigation-menu-item" mat-menu-item>
|
||||
<fuse-horizontal-navigation-divider-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-horizontal-navigation-divider-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
|
||||
<!-- Item template -->
|
||||
<ng-template let-item #itemTemplate>
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-wrapper"
|
||||
[class.fuse-horizontal-navigation-item-has-subtitle]="!!item.subtitle"
|
||||
[ngClass]="item.classes?.wrapper"
|
||||
>
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-horizontal-navigation-item-disabled': item.disabled,
|
||||
'fuse-horizontal-navigation-item-active-forced': item.active
|
||||
}"
|
||||
[matTooltip]="item.tooltip || ''"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item.icon">
|
||||
<mat-icon
|
||||
class="fuse-horizontal-navigation-item-icon"
|
||||
[ngClass]="item.classes?.icon"
|
||||
[svgIcon]="item.icon"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-horizontal-navigation-item-title-wrapper">
|
||||
<div class="fuse-horizontal-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title
|
||||
? item.classes.title
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item.subtitle">
|
||||
<div class="fuse-horizontal-navigation-item-subtitle text-hint">
|
||||
<span [ngClass]="item.classes?.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item.badge">
|
||||
<div class="fuse-horizontal-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-badge-content"
|
||||
[ngClass]="item.badge.classes"
|
||||
>
|
||||
{{ item.badge.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { BooleanInput } from '@angular/cdk/coercion';
|
||||
import { MatMenu } from '@angular/material/menu';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-horizontal-navigation-branch-item',
|
||||
templateUrl: './branch.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseHorizontalNavigationBranchItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_child: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() child: boolean = false;
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
@ViewChild('matMenu', { static: true }) matMenu!: MatMenu;
|
||||
|
||||
private _fuseHorizontalNavigationComponent?: FuseHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseHorizontalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseHorizontalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Trigger the change detection
|
||||
*/
|
||||
triggerChangeDetection(): void {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any {
|
||||
return item.id || index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<!-- Divider -->
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-wrapper divider"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
></div>
|
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-horizontal-navigation-divider-item',
|
||||
templateUrl: './divider.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseHorizontalNavigationDividerItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
private _fuseHorizontalNavigationComponent?: FuseHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseHorizontalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseHorizontalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<!-- Spacer -->
|
||||
<div
|
||||
class="fuse-horizontal-navigation-item-wrapper"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
></div>
|
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-horizontal-navigation-spacer-item',
|
||||
templateUrl: './spacer.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseHorizontalNavigationSpacerItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
private _fuseHorizontalNavigationComponent?: FuseHorizontalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseHorizontalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseHorizontalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<div class="fuse-horizontal-navigation-wrapper">
|
||||
|
||||
<ng-container *ngFor="let item of navigation; trackBy: trackByFn">
|
||||
|
||||
<!-- Skip the hidden items -->
|
||||
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
|
||||
|
||||
<!-- Basic -->
|
||||
<ng-container *ngIf="item.type === 'basic'">
|
||||
<fuse-horizontal-navigation-basic-item
|
||||
class="fuse-horizontal-navigation-menu-item"
|
||||
[item]="item"
|
||||
[name]="name"></fuse-horizontal-navigation-basic-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Branch: aside, collapsable, group -->
|
||||
<ng-container *ngIf="item.type === 'aside' || item.type === 'collapsable' || item.type === 'group'">
|
||||
<fuse-horizontal-navigation-branch-item
|
||||
class="fuse-horizontal-navigation-menu-item"
|
||||
[item]="item"
|
||||
[name]="name"></fuse-horizontal-navigation-branch-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Spacer -->
|
||||
<ng-container *ngIf="item.type === 'spacer'">
|
||||
<fuse-horizontal-navigation-spacer-item
|
||||
class="fuse-horizontal-navigation-menu-item"
|
||||
[item]="item"
|
||||
[name]="name"></fuse-horizontal-navigation-spacer-item>
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</ng-container>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,180 @@
|
|||
/* Root navigation specific */
|
||||
fuse-horizontal-navigation {
|
||||
|
||||
.fuse-horizontal-navigation-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
/* Basic, Branch */
|
||||
fuse-horizontal-navigation-basic-item,
|
||||
fuse-horizontal-navigation-branch-item {
|
||||
|
||||
@screen sm {
|
||||
|
||||
&:hover {
|
||||
|
||||
.fuse-horizontal-navigation-item-wrapper {
|
||||
@apply bg-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-wrapper {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.fuse-horizontal-navigation-item {
|
||||
padding: 0 16px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
.fuse-horizontal-navigation-item-icon {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Basic - When item active (current link) */
|
||||
fuse-horizontal-navigation-basic-item {
|
||||
|
||||
.fuse-horizontal-navigation-item-active,
|
||||
.fuse-horizontal-navigation-item-active-forced {
|
||||
|
||||
.fuse-horizontal-navigation-item-title {
|
||||
@apply text-primary #{'!important'};
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-subtitle {
|
||||
@apply text-primary-400 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
@apply text-primary-600 #{'!important'};
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-icon {
|
||||
@apply text-primary #{'!important'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Branch - When menu open */
|
||||
fuse-horizontal-navigation-branch-item {
|
||||
|
||||
.fuse-horizontal-navigation-menu-active,
|
||||
.fuse-horizontal-navigation-menu-active-forced {
|
||||
|
||||
.fuse-horizontal-navigation-item-wrapper {
|
||||
@apply bg-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Spacer */
|
||||
fuse-horizontal-navigation-spacer-item {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Menu panel specific */
|
||||
.fuse-horizontal-navigation-menu-panel {
|
||||
|
||||
.fuse-horizontal-navigation-menu-item {
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
line-height: normal;
|
||||
white-space: normal;
|
||||
|
||||
/* Basic, Branch */
|
||||
fuse-horizontal-navigation-basic-item,
|
||||
fuse-horizontal-navigation-branch-item,
|
||||
fuse-horizontal-navigation-divider-item {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
fuse-horizontal-navigation-divider-item {
|
||||
margin: 8px -16px;
|
||||
|
||||
.fuse-horizontal-navigation-item-wrapper {
|
||||
height: 1px;
|
||||
box-shadow: 0 1px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation menu item common */
|
||||
.fuse-horizontal-navigation-menu-item {
|
||||
|
||||
/* Basic - When item active (current link) */
|
||||
fuse-horizontal-navigation-basic-item {
|
||||
|
||||
.fuse-horizontal-navigation-item-active,
|
||||
.fuse-horizontal-navigation-item-active-forced {
|
||||
|
||||
.fuse-horizontal-navigation-item-title {
|
||||
@apply text-primary #{'!important'};
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-subtitle {
|
||||
@apply text-primary-400 #{'!important'};
|
||||
|
||||
.dark & {
|
||||
@apply text-primary-600 #{'!important'};
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-icon {
|
||||
@apply text-primary #{'!important'};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-wrapper {
|
||||
width: 100%;
|
||||
|
||||
&.fuse-horizontal-navigation-item-has-subtitle {
|
||||
|
||||
.fuse-horizontal-navigation-item {
|
||||
min-height: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 48px;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
|
||||
.fuse-horizontal-navigation-item-title-wrapper {
|
||||
|
||||
.fuse-horizontal-navigation-item-subtitle {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.fuse-horizontal-navigation-item-badge {
|
||||
margin-left: auto;
|
||||
|
||||
.fuse-horizontal-navigation-item-badge-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { ReplaySubject, Subject } from 'rxjs';
|
||||
import { fuseAnimations } from '@fuse/animations';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-horizontal-navigation',
|
||||
templateUrl: './horizontal.component.html',
|
||||
styleUrls: ['./horizontal.component.scss'],
|
||||
animations: fuseAnimations,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
exportAs: 'fuseHorizontalNavigation',
|
||||
})
|
||||
export class FuseHorizontalNavigationComponent
|
||||
implements OnChanges, OnInit, OnDestroy
|
||||
{
|
||||
@Input() name: string = this._fuseUtilsService.randomId();
|
||||
@Input() navigation?: FuseNavigationItem[];
|
||||
|
||||
onRefreshed: ReplaySubject<boolean> = new ReplaySubject<boolean>(1);
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Navigation
|
||||
if ('navigation' in changes) {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Make sure the name input is not an empty string
|
||||
if (this.name === '') {
|
||||
this.name = this._fuseUtilsService.randomId();
|
||||
}
|
||||
|
||||
// Register the navigation component
|
||||
this._fuseNavigationService.registerComponent(this.name, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Deregister the navigation component from the registry
|
||||
this._fuseNavigationService.deregisterComponent(this.name);
|
||||
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Refresh the component to apply the changes
|
||||
*/
|
||||
refresh(): void {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Execute the observable
|
||||
this.onRefreshed.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any {
|
||||
return item.id || index;
|
||||
}
|
||||
}
|
1
src/@fuse/components/navigation/index.ts
Normal file
1
src/@fuse/components/navigation/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from '@fuse/components/navigation/public-api';
|
55
src/@fuse/components/navigation/navigation.module.ts
Normal file
55
src/@fuse/components/navigation/navigation.module.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { FuseScrollbarModule } from '@fuse/directives/scrollbar/public-api';
|
||||
import { FuseHorizontalNavigationBasicItemComponent } from '@fuse/components/navigation/horizontal/components/basic/basic.component';
|
||||
import { FuseHorizontalNavigationBranchItemComponent } from '@fuse/components/navigation/horizontal/components/branch/branch.component';
|
||||
import { FuseHorizontalNavigationDividerItemComponent } from '@fuse/components/navigation/horizontal/components/divider/divider.component';
|
||||
import { FuseHorizontalNavigationSpacerItemComponent } from '@fuse/components/navigation/horizontal/components/spacer/spacer.component';
|
||||
import { FuseHorizontalNavigationComponent } from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
import { FuseVerticalNavigationAsideItemComponent } from '@fuse/components/navigation/vertical/components/aside/aside.component';
|
||||
import { FuseVerticalNavigationBasicItemComponent } from '@fuse/components/navigation/vertical/components/basic/basic.component';
|
||||
import { FuseVerticalNavigationCollapsableItemComponent } from '@fuse/components/navigation/vertical/components/collapsable/collapsable.component';
|
||||
import { FuseVerticalNavigationDividerItemComponent } from '@fuse/components/navigation/vertical/components/divider/divider.component';
|
||||
import { FuseVerticalNavigationGroupItemComponent } from '@fuse/components/navigation/vertical/components/group/group.component';
|
||||
import { FuseVerticalNavigationSpacerItemComponent } from '@fuse/components/navigation/vertical/components/spacer/spacer.component';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FuseHorizontalNavigationBasicItemComponent,
|
||||
FuseHorizontalNavigationBranchItemComponent,
|
||||
FuseHorizontalNavigationDividerItemComponent,
|
||||
FuseHorizontalNavigationSpacerItemComponent,
|
||||
FuseHorizontalNavigationComponent,
|
||||
FuseVerticalNavigationAsideItemComponent,
|
||||
FuseVerticalNavigationBasicItemComponent,
|
||||
FuseVerticalNavigationCollapsableItemComponent,
|
||||
FuseVerticalNavigationDividerItemComponent,
|
||||
FuseVerticalNavigationGroupItemComponent,
|
||||
FuseVerticalNavigationSpacerItemComponent,
|
||||
FuseVerticalNavigationComponent
|
||||
],
|
||||
imports : [
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
MatButtonModule,
|
||||
MatDividerModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
MatTooltipModule,
|
||||
FuseScrollbarModule
|
||||
],
|
||||
exports : [
|
||||
FuseHorizontalNavigationComponent,
|
||||
FuseVerticalNavigationComponent
|
||||
]
|
||||
})
|
||||
export class FuseNavigationModule
|
||||
{
|
||||
}
|
182
src/@fuse/components/navigation/navigation.service.ts
Normal file
182
src/@fuse/components/navigation/navigation.service.ts
Normal file
|
@ -0,0 +1,182 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FuseNavigationService {
|
||||
private _componentRegistry: Map<string, any> = new Map<string, any>();
|
||||
private _navigationStore: Map<string, FuseNavigationItem[]> = new Map<
|
||||
string,
|
||||
any
|
||||
>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor() {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Register navigation component
|
||||
*
|
||||
* @param name
|
||||
* @param component
|
||||
*/
|
||||
registerComponent(name: string, component: any): void {
|
||||
this._componentRegistry.set(name, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregister navigation component
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
deregisterComponent(name: string): void {
|
||||
this._componentRegistry.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get navigation component from the registry
|
||||
*
|
||||
* @param name
|
||||
*/
|
||||
getComponent<T>(name?: string): T | undefined {
|
||||
if (!name) {
|
||||
return undefined;
|
||||
}
|
||||
return this._componentRegistry.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given navigation with the given key
|
||||
*
|
||||
* @param key
|
||||
* @param navigation
|
||||
*/
|
||||
storeNavigation(key: string, navigation: FuseNavigationItem[]): void {
|
||||
// Add to the store
|
||||
this._navigationStore.set(key, navigation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get navigation from storage by key
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
getNavigation(key: string): FuseNavigationItem[] {
|
||||
return this._navigationStore.get(key) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the navigation from the storage
|
||||
*
|
||||
* @param key
|
||||
*/
|
||||
deleteNavigation(key: string): void {
|
||||
// Check if the navigation exists
|
||||
if (!this._navigationStore.has(key)) {
|
||||
console.warn(
|
||||
`Navigation with the key '${key}' does not exist in the store.`
|
||||
);
|
||||
}
|
||||
|
||||
// Delete from the storage
|
||||
this._navigationStore.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that returns a flattened
|
||||
* version of the given navigation array
|
||||
*
|
||||
* @param navigation
|
||||
* @param flatNavigation
|
||||
*/
|
||||
getFlatNavigation(
|
||||
navigation: FuseNavigationItem[],
|
||||
flatNavigation: FuseNavigationItem[] = []
|
||||
): FuseNavigationItem[] {
|
||||
for (const item of navigation) {
|
||||
if (item.type === 'basic') {
|
||||
flatNavigation.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
item.type === 'aside' ||
|
||||
item.type === 'collapsable' ||
|
||||
item.type === 'group'
|
||||
) {
|
||||
if (item.children) {
|
||||
this.getFlatNavigation(item.children, flatNavigation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flatNavigation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that returns the item
|
||||
* with the given id from given navigation
|
||||
*
|
||||
* @param id
|
||||
* @param navigation
|
||||
*/
|
||||
getItem(
|
||||
navigation: FuseNavigationItem[],
|
||||
id?: string
|
||||
): FuseNavigationItem | null {
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
for (const item of navigation) {
|
||||
if (item.id === id) {
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
const childItem = this.getItem(item.children, id);
|
||||
|
||||
if (childItem) {
|
||||
return childItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function that returns the item's parent
|
||||
* with the given id from given navigation
|
||||
*
|
||||
* @param id
|
||||
* @param navigation
|
||||
* @param parent
|
||||
*/
|
||||
getItemParent(
|
||||
id: string,
|
||||
navigation: FuseNavigationItem[],
|
||||
parent: FuseNavigationItem[] | FuseNavigationItem
|
||||
): FuseNavigationItem[] | FuseNavigationItem | null {
|
||||
for (const item of navigation) {
|
||||
if (item.id === id) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
const childItem = this.getItemParent(id, item.children, item);
|
||||
|
||||
if (childItem) {
|
||||
return childItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
41
src/@fuse/components/navigation/navigation.types.ts
Normal file
41
src/@fuse/components/navigation/navigation.types.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
|
||||
export interface FuseNavigationItem {
|
||||
id?: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
type: 'aside' | 'basic' | 'collapsable' | 'divider' | 'group' | 'spacer';
|
||||
hidden?: (item: FuseNavigationItem) => boolean;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
link?: string;
|
||||
externalLink?: boolean;
|
||||
target?: '_blank' | '_self' | '_parent' | '_top' | string;
|
||||
exactMatch?: boolean;
|
||||
isActiveMatchOptions?: IsActiveMatchOptions;
|
||||
function?: (item?: FuseNavigationItem) => void;
|
||||
classes?: {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
icon?: string;
|
||||
wrapper?: string;
|
||||
};
|
||||
icon?: string;
|
||||
badge?: {
|
||||
title?: string;
|
||||
classes?: string;
|
||||
};
|
||||
children?: FuseNavigationItem[];
|
||||
meta?: any;
|
||||
}
|
||||
|
||||
export type FuseVerticalNavigationAppearance =
|
||||
| 'default'
|
||||
| 'compact'
|
||||
| 'dense'
|
||||
| 'thin';
|
||||
|
||||
export type FuseVerticalNavigationMode = 'over' | 'side';
|
||||
|
||||
export type FuseVerticalNavigationPosition = 'left' | 'right';
|
5
src/@fuse/components/navigation/public-api.ts
Normal file
5
src/@fuse/components/navigation/public-api.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export * from '@fuse/components/navigation/horizontal/horizontal.component';
|
||||
export * from '@fuse/components/navigation/vertical/vertical.component';
|
||||
export * from '@fuse/components/navigation/navigation.module';
|
||||
export * from '@fuse/components/navigation/navigation.service';
|
||||
export * from '@fuse/components/navigation/navigation.types';
|
|
@ -0,0 +1,126 @@
|
|||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper"
|
||||
[class.fuse-vertical-navigation-item-has-subtitle]="!!item?.subtitle"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-vertical-navigation-item-active': active,
|
||||
'fuse-vertical-navigation-item-disabled': item?.disabled,
|
||||
'fuse-vertical-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item?.icon">
|
||||
<mat-icon
|
||||
class="fuse-vertical-navigation-item-icon"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.icon
|
||||
? item.classes.icon
|
||||
: ''
|
||||
"
|
||||
[svgIcon]="!!item && !!item.icon ? item.icon : ''"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-vertical-navigation-item-title-wrapper">
|
||||
<div class="fuse-vertical-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title
|
||||
? item.classes.title
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item?.subtitle">
|
||||
<div class="fuse-vertical-navigation-item-subtitle">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.subtitle
|
||||
? item.classes.subtitle
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item?.badge">
|
||||
<div class="fuse-vertical-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-badge-content"
|
||||
[ngClass]="
|
||||
!!item && !!item.badge && !!item.badge.classes
|
||||
? item.badge.classes
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.badge?.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!skipChildren">
|
||||
<div class="fuse-vertical-navigation-item-children">
|
||||
<ng-container *ngFor="let item of item?.children; trackBy: trackByFn">
|
||||
<!-- Skip the hidden items -->
|
||||
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
|
||||
<!-- Basic -->
|
||||
<ng-container *ngIf="item.type === 'basic'">
|
||||
<fuse-vertical-navigation-basic-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-basic-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Collapsable -->
|
||||
<ng-container *ngIf="item.type === 'collapsable'">
|
||||
<fuse-vertical-navigation-collapsable-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
[autoCollapse]="autoCollapse"
|
||||
></fuse-vertical-navigation-collapsable-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Divider -->
|
||||
<ng-container *ngIf="item.type === 'divider'">
|
||||
<fuse-vertical-navigation-divider-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-divider-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Group -->
|
||||
<ng-container *ngIf="item.type === 'group'">
|
||||
<fuse-vertical-navigation-group-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-group-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Spacer -->
|
||||
<ng-container *ngIf="item.type === 'spacer'">
|
||||
<fuse-vertical-navigation-spacer-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-spacer-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,187 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { BooleanInput } from '@angular/cdk/coercion';
|
||||
import { filter, Subject, takeUntil } from 'rxjs';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-vertical-navigation-aside-item',
|
||||
templateUrl: './aside.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseVerticalNavigationAsideItemComponent
|
||||
implements OnChanges, OnInit, OnDestroy
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_autoCollapse: BooleanInput;
|
||||
static ngAcceptInputType_skipChildren: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() activeItemId?: string;
|
||||
@Input() autoCollapse?: boolean;
|
||||
@Input() item!: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
@Input() skipChildren?: boolean;
|
||||
|
||||
active: boolean = false;
|
||||
private _fuseVerticalNavigationComponent?: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _router: Router,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On changes
|
||||
*
|
||||
* @param changes
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
// Active item id
|
||||
if ('activeItemId' in changes) {
|
||||
// Mark if active
|
||||
this._markIfActive(this._router.url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Mark if active
|
||||
this._markIfActive(this._router.url);
|
||||
|
||||
// Attach a listener to the NavigationEnd event
|
||||
this._router.events
|
||||
.pipe(
|
||||
filter(
|
||||
(event): event is NavigationEnd => event instanceof NavigationEnd
|
||||
),
|
||||
takeUntil(this._unsubscribeAll)
|
||||
)
|
||||
.subscribe((event: NavigationEnd) => {
|
||||
// Mark if active
|
||||
this._markIfActive(event.urlAfterRedirects);
|
||||
});
|
||||
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any {
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if the given item has the given url
|
||||
* in one of its children
|
||||
*
|
||||
* @param item
|
||||
* @param currentUrl
|
||||
* @private
|
||||
*/
|
||||
private _hasActiveChild(
|
||||
item: FuseNavigationItem,
|
||||
currentUrl: string
|
||||
): boolean {
|
||||
const children = item.children;
|
||||
|
||||
if (!children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const child of children) {
|
||||
if (child.children) {
|
||||
if (this._hasActiveChild(child, currentUrl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip items other than 'basic'
|
||||
if (child.type !== 'basic') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the child has a link and is active
|
||||
if (
|
||||
child.link &&
|
||||
this._router.isActive(child.link, child.exactMatch || false)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide and mark if the item is active
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private _markIfActive(currentUrl: string): void {
|
||||
// Check if the activeItemId is equals to this item id
|
||||
this.active = this.activeItemId === this.item?.id;
|
||||
|
||||
// If the aside has a children that is active,
|
||||
// always mark it as active
|
||||
if (this._hasActiveChild(this.item, currentUrl)) {
|
||||
this.active = true;
|
||||
}
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
<!-- Item wrapper -->
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper"
|
||||
[class.fuse-vertical-navigation-item-has-subtitle]="!!item?.subtitle"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<!-- Item with an internal link -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && !item?.externalLink && !item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-vertical-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[routerLink]="[item?.link]"
|
||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an external link -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && item?.externalLink && !item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-vertical-navigation-item"
|
||||
[href]="item?.link"
|
||||
[target]="item?.target || '_self'"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with a function -->
|
||||
<ng-container *ngIf="!item?.link && item?.function && !item?.disabled">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-vertical-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an internal link and function -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && !item?.externalLink && item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-vertical-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[routerLink]="[item?.link]"
|
||||
[routerLinkActive]="'fuse-vertical-navigation-item-active'"
|
||||
[routerLinkActiveOptions]="isActiveMatchOptions"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with an external link and function -->
|
||||
<ng-container
|
||||
*ngIf="
|
||||
item?.link && item?.externalLink && item?.function && !item?.disabled
|
||||
"
|
||||
>
|
||||
<a
|
||||
class="fuse-vertical-navigation-item"
|
||||
[href]="item?.link"
|
||||
[target]="item?.target || '_self'"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="item?.function(item)"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item with a no link and no function -->
|
||||
<ng-container *ngIf="!item?.link && !item?.function && !item?.disabled">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{
|
||||
'fuse-vertical-navigation-item-active-forced': item?.active
|
||||
}"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Item is disabled -->
|
||||
<ng-container *ngIf="item?.disabled">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item fuse-vertical-navigation-item-disabled"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
>
|
||||
<ng-container *ngTemplateOutlet="itemTemplate"></ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Item template -->
|
||||
<ng-template #itemTemplate>
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item?.icon">
|
||||
<mat-icon
|
||||
class="fuse-vertical-navigation-item-icon"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.icon ? item.classes.icon : ''
|
||||
"
|
||||
[svgIcon]="!!item && !!item.icon ? item.icon : ''"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-vertical-navigation-item-title-wrapper">
|
||||
<div class="fuse-vertical-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title ? item.classes.title : ''
|
||||
"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item?.subtitle">
|
||||
<div class="fuse-vertical-navigation-item-subtitle">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.subtitle
|
||||
? item.classes.subtitle
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item?.badge">
|
||||
<div class="fuse-vertical-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-badge-content"
|
||||
[ngClass]="
|
||||
!!item && !!item.badge && !!item.badge.classes
|
||||
? item.badge.classes
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.badge?.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
|
@ -0,0 +1,86 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { IsActiveMatchOptions } from '@angular/router';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
import { FuseUtilsService } from '@fuse/services/utils/utils.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-vertical-navigation-basic-item',
|
||||
templateUrl: './basic.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseVerticalNavigationBasicItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
isActiveMatchOptions: IsActiveMatchOptions;
|
||||
private _fuseVerticalNavigationComponent?: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService,
|
||||
private _fuseUtilsService: FuseUtilsService
|
||||
) {
|
||||
// Set the equivalent of {exact: false} as default for active match options.
|
||||
// We are not assigning the item.isActiveMatchOptions directly to the
|
||||
// [routerLinkActiveOptions] because if it's "undefined" initially, the router
|
||||
// will throw an error and stop working.
|
||||
this.isActiveMatchOptions = this._fuseUtilsService.subsetMatchOptions;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Set the "isActiveMatchOptions" either from item's
|
||||
// "isActiveMatchOptions" or the equivalent form of
|
||||
// item's "exactMatch" option
|
||||
this.isActiveMatchOptions =
|
||||
this.item?.isActiveMatchOptions ?? this.item?.exactMatch
|
||||
? this._fuseUtilsService.exactMatchOptions
|
||||
: this._fuseUtilsService.subsetMatchOptions;
|
||||
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper"
|
||||
[class.fuse-vertical-navigation-item-has-subtitle]="!!item?.subtitle"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && item.classes.wrapper ? item.classes.wrapper : ''
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="fuse-vertical-navigation-item"
|
||||
[ngClass]="{ 'fuse-vertical-navigation-item-disabled': item?.disabled }"
|
||||
[matTooltip]="item?.tooltip || ''"
|
||||
(click)="toggleCollapsable()"
|
||||
>
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item?.icon">
|
||||
<mat-icon
|
||||
class="fuse-vertical-navigation-item-icon"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && item.classes.icon ? item.classes.icon : ''
|
||||
"
|
||||
[svgIcon]="!!item && !!item.icon ? item.icon : ''"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-vertical-navigation-item-title-wrapper">
|
||||
<div class="fuse-vertical-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title
|
||||
? item.classes.title
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item?.subtitle">
|
||||
<div class="fuse-vertical-navigation-item-subtitle">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.subtitle
|
||||
? item.classes.subtitle
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item?.badge">
|
||||
<div class="fuse-vertical-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-badge-content"
|
||||
[ngClass]="
|
||||
!!item && !!item.badge && !!item.badge.classes
|
||||
? item.badge.classes
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.badge?.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<!-- Arrow -->
|
||||
<mat-icon
|
||||
class="fuse-vertical-navigation-item-arrow icon-size-4"
|
||||
[svgIcon]="'heroicons_solid:chevron-right'"
|
||||
></mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-children"
|
||||
*ngIf="!isCollapsed"
|
||||
@expandCollapse
|
||||
>
|
||||
<ng-container *ngFor="let item of item?.children; trackBy: trackByFn">
|
||||
<!-- Skip the hidden items -->
|
||||
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
|
||||
<!-- Basic -->
|
||||
<ng-container *ngIf="item.type === 'basic'">
|
||||
<fuse-vertical-navigation-basic-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-basic-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Collapsable -->
|
||||
<ng-container *ngIf="item.type === 'collapsable'">
|
||||
<fuse-vertical-navigation-collapsable-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
[autoCollapse]="autoCollapse"
|
||||
></fuse-vertical-navigation-collapsable-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Divider -->
|
||||
<ng-container *ngIf="item.type === 'divider'">
|
||||
<fuse-vertical-navigation-divider-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-divider-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Group -->
|
||||
<ng-container *ngIf="item.type === 'group'">
|
||||
<fuse-vertical-navigation-group-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-group-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Spacer -->
|
||||
<ng-container *ngIf="item.type === 'spacer'">
|
||||
<fuse-vertical-navigation-spacer-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-spacer-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
|
@ -0,0 +1,325 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
HostBinding,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { BooleanInput } from '@angular/cdk/coercion';
|
||||
import { filter, Subject, takeUntil } from 'rxjs';
|
||||
import { fuseAnimations } from '@fuse/animations';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-vertical-navigation-collapsable-item',
|
||||
templateUrl: './collapsable.component.html',
|
||||
animations: fuseAnimations,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseVerticalNavigationCollapsableItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_autoCollapse: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() autoCollapse: boolean = true;
|
||||
@Input() item!: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
isCollapsed: boolean = true;
|
||||
isExpanded: boolean = false;
|
||||
private _fuseVerticalNavigationComponent?: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _router: Router,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Accessors
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Host binding for component classes
|
||||
*/
|
||||
@HostBinding('class') get classList(): any {
|
||||
return {
|
||||
'fuse-vertical-navigation-item-collapsed': this.isCollapsed,
|
||||
'fuse-vertical-navigation-item-expanded': this.isExpanded,
|
||||
};
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// If the item has a children that has a matching url with the current url, expand...
|
||||
if (this._hasActiveChild(this.item, this._router.url)) {
|
||||
this.expand();
|
||||
}
|
||||
// Otherwise...
|
||||
else {
|
||||
// If the autoCollapse is on, collapse...
|
||||
if (this.autoCollapse) {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for the onCollapsableItemCollapsed from the service
|
||||
this._fuseVerticalNavigationComponent?.onCollapsableItemCollapsed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((collapsedItem) => {
|
||||
// Check if the collapsed item is null
|
||||
if (collapsedItem === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collapse if this is a children of the collapsed item
|
||||
if (this._isChildrenOf(collapsedItem, this.item)) {
|
||||
this.collapse();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for the onCollapsableItemExpanded from the service if the autoCollapse is on
|
||||
if (this.autoCollapse) {
|
||||
this._fuseVerticalNavigationComponent?.onCollapsableItemExpanded
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe((expandedItem) => {
|
||||
// Check if the expanded item is null
|
||||
if (expandedItem === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a parent of the expanded item
|
||||
if (this._isChildrenOf(this.item, expandedItem)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this has a children with a matching url with the current active url
|
||||
if (this._hasActiveChild(this.item, this._router.url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the expanded item
|
||||
if (this.item === expandedItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If none of the above conditions are matched, collapse this item
|
||||
this.collapse();
|
||||
});
|
||||
}
|
||||
|
||||
// Attach a listener to the NavigationEnd event
|
||||
this._router.events
|
||||
.pipe(
|
||||
filter(
|
||||
(event): event is NavigationEnd => event instanceof NavigationEnd
|
||||
),
|
||||
takeUntil(this._unsubscribeAll)
|
||||
)
|
||||
.subscribe((event: NavigationEnd) => {
|
||||
// If the item has a children that has a matching url with the current url, expand...
|
||||
if (this._hasActiveChild(this.item, event.urlAfterRedirects)) {
|
||||
this.expand();
|
||||
}
|
||||
// Otherwise...
|
||||
else {
|
||||
// If the autoCollapse is on, collapse...
|
||||
if (this.autoCollapse) {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Collapse
|
||||
*/
|
||||
collapse(): void {
|
||||
// Return if the item is disabled
|
||||
if (this.item?.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if the item is already collapsed
|
||||
if (this.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collapse it
|
||||
this.isCollapsed = true;
|
||||
this.isExpanded = !this.isCollapsed;
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Execute the observable
|
||||
this._fuseVerticalNavigationComponent?.onCollapsableItemCollapsed.next(
|
||||
this.item
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand
|
||||
*/
|
||||
expand(): void {
|
||||
// Return if the item is disabled
|
||||
if (this.item?.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if the item is already expanded
|
||||
if (!this.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand it
|
||||
this.isCollapsed = false;
|
||||
this.isExpanded = !this.isCollapsed;
|
||||
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
|
||||
// Execute the observable
|
||||
this._fuseVerticalNavigationComponent?.onCollapsableItemExpanded.next(
|
||||
this.item
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle collapsable
|
||||
*/
|
||||
toggleCollapsable(): void {
|
||||
// Toggle collapse/expand
|
||||
if (this.isCollapsed) {
|
||||
this.expand();
|
||||
} else {
|
||||
this.collapse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any {
|
||||
return item.id || index;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Check if the given item has the given url
|
||||
* in one of its children
|
||||
*
|
||||
* @param item
|
||||
* @param currentUrl
|
||||
* @private
|
||||
*/
|
||||
private _hasActiveChild(
|
||||
item: FuseNavigationItem,
|
||||
currentUrl: string
|
||||
): boolean {
|
||||
const children = item.children;
|
||||
|
||||
if (!children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const child of children) {
|
||||
if (child.children) {
|
||||
if (this._hasActiveChild(child, currentUrl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the child has a link and is active
|
||||
if (
|
||||
child.link &&
|
||||
this._router.isActive(child.link, child.exactMatch || false)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a children
|
||||
* of the given item
|
||||
*
|
||||
* @param parent
|
||||
* @param item
|
||||
* @private
|
||||
*/
|
||||
private _isChildrenOf(
|
||||
parent: FuseNavigationItem,
|
||||
item: FuseNavigationItem
|
||||
): boolean {
|
||||
const children = parent.children;
|
||||
|
||||
if (!children) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (children.indexOf(item) > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const child of children) {
|
||||
if (child.children) {
|
||||
if (this._isChildrenOf(child, item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<!-- Divider -->
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper divider"
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.wrapper
|
||||
? item.classes.wrapper
|
||||
: ''
|
||||
"
|
||||
></div>
|
|
@ -0,0 +1,65 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-vertical-navigation-divider-item',
|
||||
templateUrl: './divider.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseVerticalNavigationDividerItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
private _fuseVerticalNavigationComponent?: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<!-- Item wrapper -->
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper"
|
||||
[class.fuse-vertical-navigation-item-has-subtitle]="!!item?.subtitle"
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.wrapper ? item.classes.wrapper : ''
|
||||
"
|
||||
>
|
||||
<div class="fuse-vertical-navigation-item">
|
||||
<!-- Icon -->
|
||||
<ng-container *ngIf="item?.icon">
|
||||
<mat-icon
|
||||
class="fuse-vertical-navigation-item-icon"
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.icon ? item.classes.icon : ''
|
||||
"
|
||||
[svgIcon]="!!item && !!item.icon ? item.icon : ''"
|
||||
></mat-icon>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title & Subtitle -->
|
||||
<div class="fuse-vertical-navigation-item-title-wrapper">
|
||||
<div class="fuse-vertical-navigation-item-title">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.title
|
||||
? item.classes.title
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-container *ngIf="item?.subtitle">
|
||||
<div class="fuse-vertical-navigation-item-subtitle">
|
||||
<span
|
||||
[ngClass]="
|
||||
!!item && !!item.classes && !!item.classes.subtitle
|
||||
? item.classes.subtitle
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.subtitle }}
|
||||
</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Badge -->
|
||||
<ng-container *ngIf="item?.badge">
|
||||
<div class="fuse-vertical-navigation-item-badge">
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-badge-content"
|
||||
[ngClass]="
|
||||
!!item && !!item.badge && !!item.badge.classes
|
||||
? item.badge.classes
|
||||
: ''
|
||||
"
|
||||
>
|
||||
{{ item?.badge?.title }}
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-container *ngFor="let item of item?.children; trackBy: trackByFn">
|
||||
<!-- Skip the hidden items -->
|
||||
<ng-container *ngIf="(item.hidden && !item.hidden(item)) || !item.hidden">
|
||||
<!-- Basic -->
|
||||
<ng-container *ngIf="item.type === 'basic'">
|
||||
<fuse-vertical-navigation-basic-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-basic-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Collapsable -->
|
||||
<ng-container *ngIf="item.type === 'collapsable'">
|
||||
<fuse-vertical-navigation-collapsable-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
[autoCollapse]="autoCollapse"
|
||||
></fuse-vertical-navigation-collapsable-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Divider -->
|
||||
<ng-container *ngIf="item.type === 'divider'">
|
||||
<fuse-vertical-navigation-divider-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-divider-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Group -->
|
||||
<ng-container *ngIf="item.type === 'group'">
|
||||
<fuse-vertical-navigation-group-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-group-item>
|
||||
</ng-container>
|
||||
|
||||
<!-- Spacer -->
|
||||
<ng-container *ngIf="item.type === 'spacer'">
|
||||
<fuse-vertical-navigation-spacer-item
|
||||
[item]="item"
|
||||
[name]="name"
|
||||
></fuse-vertical-navigation-spacer-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { BooleanInput } from '@angular/cdk/coercion';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { FuseVerticalNavigationComponent } from '@fuse/components/navigation/vertical/vertical.component';
|
||||
import { FuseNavigationService } from '@fuse/components/navigation/navigation.service';
|
||||
import { FuseNavigationItem } from '@fuse/components/navigation/navigation.types';
|
||||
|
||||
@Component({
|
||||
selector: 'fuse-vertical-navigation-group-item',
|
||||
templateUrl: './group.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FuseVerticalNavigationGroupItemComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
static ngAcceptInputType_autoCollapse: BooleanInput;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
@Input() autoCollapse: boolean = false;
|
||||
@Input() item?: FuseNavigationItem;
|
||||
@Input() name?: string;
|
||||
|
||||
private _fuseVerticalNavigationComponent?: FuseVerticalNavigationComponent;
|
||||
private _unsubscribeAll: Subject<any> = new Subject<any>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
constructor(
|
||||
private _changeDetectorRef: ChangeDetectorRef,
|
||||
private _fuseNavigationService: FuseNavigationService
|
||||
) {}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Lifecycle hooks
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* On init
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
// Get the parent navigation component
|
||||
this._fuseVerticalNavigationComponent =
|
||||
this._fuseNavigationService.getComponent(this.name);
|
||||
|
||||
// Subscribe to onRefreshed on the navigation component
|
||||
this._fuseVerticalNavigationComponent?.onRefreshed
|
||||
.pipe(takeUntil(this._unsubscribeAll))
|
||||
.subscribe(() => {
|
||||
// Mark for check
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* On destroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
// Unsubscribe from all subscriptions
|
||||
this._unsubscribeAll.next(null);
|
||||
this._unsubscribeAll.complete();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Public methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Track by function for ngFor loops
|
||||
*
|
||||
* @param index
|
||||
* @param item
|
||||
*/
|
||||
trackByFn(index: number, item: any): any {
|
||||
return item.id || index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<!-- Spacer -->
|
||||
<div
|
||||
class="fuse-vertical-navigation-item-wrapper"
|
||||
[ngClass]="
|
||||
!!item && item.classes && item.classes.wrapper ? item.classes.wrapper : ''
|
||||
"
|
||||
></div>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user