initialized

This commit is contained in:
병준 박 2022-07-12 05:39:55 +00:00
parent bdcd28dc81
commit 5390ee61c4
583 changed files with 128943 additions and 79 deletions

20
.browserslistrc Normal file
View 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
View 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"

View 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": "bet-frontend-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": "bet-frontend-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"
}

View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -ex
npm install -g @angular/cli

16
.editorconfig Normal file
View 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

76
.eslintrc.json Normal file
View File

@ -0,0 +1,76 @@
{
"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/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
View 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

1
.npmrc Normal file
View File

@ -0,0 +1 @@
legacy-peer-deps=true

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
16

20
.vscode/launch.json vendored Normal file
View 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
View 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
View 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
View 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
View 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)

View File

@ -1,92 +1,27 @@
# beteran-frontend-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-frontend-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-frontend-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
View 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
View 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,
});
};

24810
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

81
package.json Normal file
View 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"
}
}

View 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';
}

View 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 };

View 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 };

View File

@ -0,0 +1 @@
export * from '@fuse/animations/public-api';

View 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
];

View 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 };

View 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 };

View 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 };

View 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>

File diff suppressed because it is too large Load Diff

View 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();
}
}

View 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
{
}

View 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);
}
}

View File

@ -0,0 +1,15 @@
export type FuseAlertAppearance =
| 'border'
| 'fill'
| 'outline'
| 'soft';
export type FuseAlertType =
| 'primary'
| 'accent'
| 'warn'
| 'basic'
| 'info'
| 'success'
| 'warning'
| 'error';

View File

@ -0,0 +1 @@
export * from '@fuse/components/alert/public-api';

View 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';

View 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>

View 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;
}
}
}

View 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);
}
}
}

View 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
{
}

View File

@ -0,0 +1,3 @@
export type FuseCardFace =
| 'front'
| 'back';

View File

@ -0,0 +1 @@
export * from '@fuse/components/card/public-api';

View File

@ -0,0 +1,2 @@
export * from '@fuse/components/card/card.component';
export * from '@fuse/components/card/card.module';

View File

@ -0,0 +1,3 @@
<div class="fuse-drawer-content">
<ng-content></ng-content>
</div>

View 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;
}
}

View 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);
}
}

View 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
{
}

View 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);
}
}

View File

@ -0,0 +1,7 @@
export type FuseDrawerMode =
| 'over'
| 'side';
export type FuseDrawerPosition =
| 'left'
| 'right';

View File

@ -0,0 +1 @@
export * from '@fuse/components/drawer/public-api';

View 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';

View 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>

View 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;
}
}
}

View 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
{
}

View 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;
}

View File

@ -0,0 +1 @@
export * from '@fuse/components/fullscreen/public-api';

View 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';

View 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 -->

View File

@ -0,0 +1,3 @@
textarea[fuse-highlight] {
display: none;
}

View 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();
}
}

View 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
{
}

View 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');
}
}

View File

@ -0,0 +1 @@
export * from '@fuse/components/highlight/public-api';

View 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';

View File

@ -0,0 +1 @@
export * from '@fuse/components/loading-bar/public-api';

View File

@ -0,0 +1,3 @@
<ng-container *ngIf="show && !!mode">
<mat-progress-bar [mode]="mode" [value]="progress"></mat-progress-bar>
</ng-container>

View File

@ -0,0 +1,7 @@
fuse-loading-bar {
position: fixed;
top: 0;
z-index: 999;
width: 100%;
height: 6px;
}

View 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();
}
}

View 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
{
}

View File

@ -0,0 +1,2 @@
export * from '@fuse/components/loading-bar/loading-bar.component';
export * from '@fuse/components/loading-bar/loading-bar.module';

View File

@ -0,0 +1 @@
export * from '@fuse/components/masonry/public-api';

View File

@ -0,0 +1,3 @@
<div class="flex">
<ng-container *ngTemplateOutlet="columnsTemplate; context: { $implicit: distributedColumns }"></ng-container>
</div>

View 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]);
}
}
}

View 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
{
}

View File

@ -0,0 +1,2 @@
export * from '@fuse/components/masonry/masonry.component';
export * from '@fuse/components/masonry/masonry.module';

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -0,0 +1,9 @@
<!-- Divider -->
<div
class="fuse-horizontal-navigation-item-wrapper divider"
[ngClass]="
!!item && !!item.classes && !!item.classes.wrapper
? item.classes.wrapper
: ''
"
></div>

View File

@ -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();
}
}

View File

@ -0,0 +1,9 @@
<!-- Spacer -->
<div
class="fuse-horizontal-navigation-item-wrapper"
[ngClass]="
!!item && !!item.classes && !!item.classes.wrapper
? item.classes.wrapper
: ''
"
></div>

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
export * from '@fuse/components/navigation/public-api';

View 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
{
}

View File

@ -0,0 +1,179 @@
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(
id: string,
navigation: FuseNavigationItem[]
): FuseNavigationItem | null {
for (const item of navigation) {
if (item.id === id) {
return item;
}
if (item.children) {
const childItem = this.getItem(id, item.children);
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;
}
}

View 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';

View 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';

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -0,0 +1,9 @@
<!-- Divider -->
<div
class="fuse-vertical-navigation-item-wrapper divider"
[ngClass]="
!!item && !!item.classes && !!item.classes.wrapper
? item.classes.wrapper
: ''
"
></div>

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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;
}
}

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