diff --git a/.dev.env b/.dev.env new file mode 100644 index 000000000..5db598c8d Binary files /dev/null and b/.dev.env differ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..88c2b9ff3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +node_modules +.github +dist +coverage +Makefile +.gitignore +*keys.ts +*.keys.json +.git-crypt +.git +Dockerfile diff --git a/.git-crypt/.gitattributes b/.git-crypt/.gitattributes new file mode 100644 index 000000000..665b10e8f --- /dev/null +++ b/.git-crypt/.gitattributes @@ -0,0 +1,4 @@ +# Do not edit this file. To specify the files to encrypt, create your own +# .gitattributes file in the directory where your files are. +* !filter !diff +*.gpg binary diff --git a/.git-crypt/keys/PROD/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg b/.git-crypt/keys/PROD/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg new file mode 100644 index 000000000..8334cb6ee Binary files /dev/null and b/.git-crypt/keys/PROD/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg differ diff --git a/.git-crypt/keys/PROD/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg b/.git-crypt/keys/PROD/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg new file mode 100644 index 000000000..468aeffe2 Binary files /dev/null and b/.git-crypt/keys/PROD/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg differ diff --git a/.git-crypt/keys/PROD/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg b/.git-crypt/keys/PROD/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg new file mode 100644 index 000000000..b93b2a239 --- /dev/null +++ b/.git-crypt/keys/PROD/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg @@ -0,0 +1,2 @@ +aȞiި qy3TH1m*le xl[P)q骋*+5", OؓE0uapڊ֡cj`N0jb̠a1s>_ @˽,us&j~W1iUyc\Ϊi˛E͛hjs%R#w)ñ֊PCs̸H巊bY B<6Ef(5t\U6\SI5'8L*yx2WIlC< +" F*h5~2Y?bFaӡ&!$8,͕_q26JJyaOA=AQ:jgS6͠`$Xw˴ H |V4*g|*sucDZmcLG2Ξ;`^Id& ԓ >dɥ7g  C)B"?AKrtx^j +Rp5PC0ijJȉj_)"dQ#/9m/_3k$. Q`C΁pZ5Ά3oEQ_T&v|fnh'dѴyd0_Q'͈hfw1 %-!欗v.&DrY+nB'*ݦdW~>Y8Fp31K#]^#~WӊHjzIyL_v4Qz: +)$0[4:/ \ No newline at end of file diff --git a/.git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg b/.git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg new file mode 100644 index 000000000..ca8297147 Binary files /dev/null and b/.git-crypt/keys/default/0/053FD5A73567E731BEFD3FC13A29B8373D5C3805.gpg differ diff --git a/.git-crypt/keys/default/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg b/.git-crypt/keys/default/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg new file mode 100644 index 000000000..9108d89f0 --- /dev/null +++ b/.git-crypt/keys/default/0/23ECA41E3A250D83D50F80F78CBFA564863D0420.gpg @@ -0,0 +1,4 @@ +^E[RIn@% +P q{(Vc29nK0qҠUrv& +[MޗZ"E){);dݬC[)U$&߾*^dg~I^,bLt^~*~Ð-}f GG+J,cwJL 'ǼB>!8N/:u Z'^j0u`Mu9'xJf +29xTaK)&˃CD6$C3 \ No newline at end of file diff --git a/.git-crypt/keys/default/0/32B9309131632B2E662F4D4F32F8CAC63675E769.gpg b/.git-crypt/keys/default/0/32B9309131632B2E662F4D4F32F8CAC63675E769.gpg new file mode 100644 index 000000000..9444c4a56 Binary files /dev/null and b/.git-crypt/keys/default/0/32B9309131632B2E662F4D4F32F8CAC63675E769.gpg differ diff --git a/.git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg b/.git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg new file mode 100644 index 000000000..f67c04ca3 Binary files /dev/null and b/.git-crypt/keys/default/0/32C62F6EE0EA5AB8E9F1CDF692A49E05B39E6B50.gpg differ diff --git a/.git-crypt/keys/default/0/485A3A2748A26A6037432E61DB07B54E0BDED209.gpg b/.git-crypt/keys/default/0/485A3A2748A26A6037432E61DB07B54E0BDED209.gpg new file mode 100644 index 000000000..cf14c8123 Binary files /dev/null and b/.git-crypt/keys/default/0/485A3A2748A26A6037432E61DB07B54E0BDED209.gpg differ diff --git a/.git-crypt/keys/default/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg b/.git-crypt/keys/default/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg new file mode 100644 index 000000000..aa6f4643f Binary files /dev/null and b/.git-crypt/keys/default/0/4FAFCE02C2C17F0DAE54B12ED3E873F11CC2872D.gpg differ diff --git a/.git-crypt/keys/default/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg b/.git-crypt/keys/default/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg new file mode 100644 index 000000000..723fe3f55 Binary files /dev/null and b/.git-crypt/keys/default/0/5C7F6A3EDA5D36861AE5FB87ABC9B3EFF295BDD1.gpg differ diff --git a/.git-crypt/keys/default/0/5F81F0858ECC9A3523088964CA21F24533D29507.gpg b/.git-crypt/keys/default/0/5F81F0858ECC9A3523088964CA21F24533D29507.gpg new file mode 100644 index 000000000..f3fffb486 --- /dev/null +++ b/.git-crypt/keys/default/0/5F81F0858ECC9A3523088964CA21F24533D29507.gpg @@ -0,0 +1,3 @@ +2?u] >\H}&ȡ%e'+v7z WA$K'тF$Z7P2+5`m_/Bݸ,MsgV/xZ 5zWliVMQۧUw=n?⹋:3$Q9139s>כ*j-)TRLt8gDUW^JJsj1;un4SӁ֜U2MZ)7d!Qt IYv,(hV7XabX`8x>آl}0wb1*x ɷ?~15k̐]vaòm@sZ3 +VTƹ 嫂m!,r2F2lߒ`aXɥIEM@ NBWZuI9PEI]gCIq(RZ)L ClcS=6fu={' +V()U R$E6 Vx\x#ȏۆ}{ & vރ2[~&%?e wOP6> $GITHUB_ENV + echo $RELEASE_VERSION + + - name: Unlock PROD secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_PROD }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_prod \ + . + + - name: Publish docker image to prod aws container registry + run: | + make login publish_prod image_tag=$RELEASE_VERSION + + - name: Deploy + run: | + TEMP=$(mktemp) + echo "${{ secrets.PROD_AWS_PRIVATE_KEY }}" > $TEMP + chmod 400 $TEMP + scp -o 'StrictHostKeyChecking no' -i $TEMP ./.prod.aws.env ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }}:. + scp -o 'StrictHostKeyChecking no' -i $TEMP ./infrastructure/aws_ec2.sh ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }}:./infrastructure/aws_ec2.sh + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.PROD_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" prod diff --git a/.github/workflows/aws-ui-cd-stage.yml b/.github/workflows/aws-ui-cd-stage.yml new file mode 100644 index 000000000..b17def912 --- /dev/null +++ b/.github/workflows/aws-ui-cd-stage.yml @@ -0,0 +1,49 @@ +name: time-tracker-ui-cd-stage + +on: + push: + tags: + - 'v*.*.*' + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: stage + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + + + - name: Unlock STAGE secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_STAGE }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_stage \ + . + + - name: Publish docker image to stage AWS container registry + run: | + make login publish image_tag=$RELEASE_VERSION + + - name: Deploy + run: | + TEMP=$(mktemp) + echo "${{ secrets.STAGE_AWS_PRIVATE_KEY }}" > $TEMP + chmod 400 $TEMP + scp -o 'StrictHostKeyChecking no' -i $TEMP ./.stage.aws.env ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }}:. + scp -o 'StrictHostKeyChecking no' -i $TEMP ./infrastructure/aws_ec2.sh ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }}:./infrastructure/aws_ec2.sh + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "chmod +x ./infrastructure/aws_ec2.sh" + ssh -o 'StrictHostKeyChecking no' -i $TEMP ${{ secrets.AWS_EC2_USER }}@${{ secrets.STAGE_UI_URL }} "./infrastructure/aws_ec2.sh $RELEASE_VERSION" stage diff --git a/.github/workflows/time-tracker-ui-cd-prod.yml b/.github/workflows/time-tracker-ui-cd-prod.yml new file mode 100644 index 000000000..c34b3587d --- /dev/null +++ b/.github/workflows/time-tracker-ui-cd-prod.yml @@ -0,0 +1,64 @@ +name: time-tracker-ui-cd-prod + +on: + workflow_dispatch # deactivate workflow and run it manually only + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: prod + WORKING_DIR: infrastructure/ + ARM_CLIENT_ID: ${{secrets.TF_ARM_CLIENT_ID}} + ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} + ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} + ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + + - name: Login to azure + uses: Azure/login@v1 + with: + creds: '{"clientId":"${{ secrets.TF_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.TF_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.TF_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TF_ARM_TENANT_ID }}"}' + + - name: Unlock PROD secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_PROD }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_prod \ + . + + - name: Publish docker image to prod azure container registry + run: | + make login publish acr=timetrackerserviceprodregistry image_tag=$RELEASE_VERSION + + - name: Setup terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.1.9 + + - name: Authenticate with the TF modules repository + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + + - name: Terraform Init + working-directory: ${{ env.WORKING_DIR }} + run: terraform init + + - name: Terraform Apply + working-directory: ${{ env.WORKING_DIR }} + run: terraform apply -lock=false -var-file="${{ env.TF_WORKSPACE }}.tfvars" -var "image_tag=$RELEASE_VERSION" -auto-approve diff --git a/.github/workflows/time-tracker-ui-cd-stage.yml b/.github/workflows/time-tracker-ui-cd-stage.yml new file mode 100644 index 000000000..0e287b426 --- /dev/null +++ b/.github/workflows/time-tracker-ui-cd-stage.yml @@ -0,0 +1,64 @@ +name: time-tracker-ui-cd-stage + +on: + workflow_dispatch # deactivate workflow and run it manually only + +jobs: + cd: + runs-on: ubuntu-latest + env: + TF_WORKSPACE: stage + WORKING_DIR: infrastructure/ + ARM_CLIENT_ID: ${{secrets.TF_ARM_CLIENT_ID}} + ARM_CLIENT_SECRET: ${{secrets.TF_ARM_CLIENT_SECRET}} + ARM_SUBSCRIPTION_ID: ${{secrets.TF_ARM_SUBSCRIPTION_ID}} + ARM_TENANT_ID: ${{secrets.TF_ARM_TENANT_ID}} + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Get the release_version + run: | + echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + echo $RELEASE_VERSION + + - name: Login to azure + uses: Azure/login@v1 + with: + creds: '{"clientId":"${{ secrets.TF_ARM_CLIENT_ID }}","clientSecret":"${{ secrets.TF_ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.TF_ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.TF_ARM_TENANT_ID }}"}' + + - name: Unlock STAGE secrets + uses: sliteteam/github-action-git-crypt-unlock@1.2.0 + env: + GIT_CRYPT_KEY: ${{ secrets.GIT_CRYPT_KEY_STAGE }} + + - name: Build the docker image + run: |- + docker build \ + --target production -t timetracker_ui -f Dockerfile_stage \ + . + + - name: Publish docker image to stage azure container registry + run: | + make login publish acr=timetrackerservicestageregistry image_tag=$RELEASE_VERSION + + - name: Setup terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_version: 1.1.9 + + - name: Authenticate with the TF modules repository + uses: webfactory/ssh-agent@v0.5.4 + with: + ssh-private-key: ${{ secrets.INFRA_TERRAFORM_MODULES_SSH_PRIV_KEY }} + + - name: Terraform Init + working-directory: ${{ env.WORKING_DIR }} + run: terraform init + + - name: Terraform Apply + working-directory: ${{ env.WORKING_DIR }} + run: terraform apply -var-file="${{ env.TF_WORKSPACE }}.tfvars" -var "image_tag=$RELEASE_VERSION" -auto-approve diff --git a/.github/workflows/time-tracker-ui-ci.yml b/.github/workflows/time-tracker-ui-ci.yml new file mode 100644 index 000000000..6b795d694 --- /dev/null +++ b/.github/workflows/time-tracker-ui-ci.yml @@ -0,0 +1,35 @@ +name: time-tracker-ui-ci + +on: + push: + branches: + - "master" + + pull_request: + branches: + - "master" + +jobs: + ci: + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{secrets.AWS_ACCESS_KEY_ID}} + AWS_SECRET_ACCESS_KEY: ${{secrets.AWS_SECRET_ACCESS_KEY}} + + strategy: + max-parallel: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: build docker + run: make build + + - name: Running tests + run: | + chmod -R 777 ./$home + make test + - name: Generate coverage report + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + run: bash <(curl -s https://codecov.io/bash) diff --git a/.gitignore b/.gitignore index 71b3b824f..64fbbec7d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,9 +40,9 @@ npm-debug.log yarn-error.log testem.log /typings -.keys.json -keys.ts -src/environments/keys.ts +debug.log +*.vscode +.hintrc # System Files .DS_Store @@ -50,3 +50,6 @@ Thumbs.db # stryker temp files .stryker-tmp + +# Terraform files +**/.terraform** diff --git a/.prod.aws.env b/.prod.aws.env new file mode 100644 index 000000000..9fae68b07 Binary files /dev/null and b/.prod.aws.env differ diff --git a/.prod.env b/.prod.env new file mode 100644 index 000000000..16f38b2fd Binary files /dev/null and b/.prod.env differ diff --git a/.stage.aws.env b/.stage.aws.env new file mode 100644 index 000000000..20e6ec6de Binary files /dev/null and b/.stage.aws.env differ diff --git a/.stage.env b/.stage.env new file mode 100644 index 000000000..de69d65a0 Binary files /dev/null and b/.stage.env differ diff --git a/Docker/Dockerfile.dev b/Docker/Dockerfile.dev new file mode 100644 index 000000000..31d0ab57d --- /dev/null +++ b/Docker/Dockerfile.dev @@ -0,0 +1,15 @@ +FROM node:14 + +ENV USERNAME timetracker +ENV HOME /home/${USERNAME} +RUN useradd --create-home -ms /bin/bash ${USERNAME} + +WORKDIR ${HOME}/time-tracker-ui +COPY package*.json ./ +RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui +RUN npm cache clean --force && npm install +COPY --chown=${USERNAME}:${USERNAME} . . + +USER ${USERNAME} +EXPOSE 4200 +CMD ${HOME}/time-tracker-ui/node_modules/.bin/ng serve --host 0.0.0.0 --disableHostCheck=false --poll 2000 diff --git a/Docker/Dockerfile.test b/Docker/Dockerfile.test new file mode 100644 index 000000000..8c6b483f3 --- /dev/null +++ b/Docker/Dockerfile.test @@ -0,0 +1,47 @@ +FROM node:14 + +ARG CHROME_VERSION=65.0.3325.181 +ARG CHROME_DRIVER_VERSION=2.37 +ENV USERNAME timetracker +ENV HOME /home/${USERNAME} +ENV CHROME_BIN /opt/google/chrome/google-chrome + +#Essential tools and xvfb +RUN apt-get update && apt-get install -y \ + software-properties-common \ + unzip \ + curl \ + wget \ + xvfb + +#Chrome browser to run the tests +RUN curl https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add \ + && wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ + && dpkg -i google-chrome-stable_current_amd64.deb || true +RUN apt-get install -y -f \ + && rm -rf /var/lib/apt/lists/* + +#Disable the SUID sandbox so that chrome can launch without being in a privileged container +RUN dpkg-divert --add --rename --divert /opt/google/chrome/google-chrome.real /opt/google/chrome/google-chrome \ + && echo "#! /bin/bash\nexec /opt/google/chrome/google-chrome.real --no-sandbox --disable-setuid-sandbox \"\$@\"" > /opt/google/chrome/google-chrome \ + && chmod 755 /opt/google/chrome/google-chrome + +#Chrome Driver +RUN mkdir -p /opt/selenium \ + && curl http://chromedriver.storage.googleapis.com/$CHROME_DRIVER_VERSION/chromedriver_linux64.zip -o /opt/selenium/chromedriver_linux64.zip \ + && cd /opt/selenium; unzip /opt/selenium/chromedriver_linux64.zip; rm -rf chromedriver_linux64.zip; ln -fs /opt/selenium/chromedriver /usr/local/bin/chromedriver; + + +RUN useradd --create-home -ms /bin/bash ${USERNAME} + +WORKDIR ${HOME}/time-tracker-ui +COPY package*.json ./ +RUN chown ${USERNAME}:${USERNAME} -R ${HOME}/time-tracker-ui +RUN npm cache clean --force && npm install +COPY --chown=${USERNAME}:${USERNAME} . . + + +USER ${USERNAME} +EXPOSE 4200 +EXPOSE 9876 +CMD npm run ci-test \ No newline at end of file diff --git a/Dockerfile_prod b/Dockerfile_prod new file mode 100644 index 000000000..3d51f5643 --- /dev/null +++ b/Dockerfile_prod @@ -0,0 +1,13 @@ +FROM node:14-alpine AS building +WORKDIR /app +COPY . /app +RUN npm cache clean --force && npm install +EXPOSE 4200 9876 +RUN source .prod.env && npm run build + + +FROM nginx:1.21 AS production +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=building /app/dist/time-tracker /usr/share/nginx/html +EXPOSE 80 + diff --git a/Dockerfile_stage b/Dockerfile_stage new file mode 100644 index 000000000..5922ed3d9 --- /dev/null +++ b/Dockerfile_stage @@ -0,0 +1,13 @@ +FROM node:14-alpine AS building +WORKDIR /app +COPY . /app +RUN npm cache clean --force && npm install +EXPOSE 4200 9876 +RUN source .stage.env && npm run build + + +FROM nginx:1.21 AS production +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=building /app/dist/time-tracker /usr/share/nginx/html +EXPOSE 80 + diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..b391b071d --- /dev/null +++ b/Makefile @@ -0,0 +1,106 @@ +override SHELL := /bin/bash + +.PHONY: help +help: ## Show this help message. + @echo 'Usage:' + @echo ' make [target] ...' + @echo + @echo 'Targets:' + @grep --no-filename -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: ## Create docker image with dependencies needed for development. + docker-compose build timetracker_ui + +.PHONY: cleanup +cleanup: ## Delete image timetracker_ui. + docker rmi timetracker_ui + +.PHONY: run +run: ## Execute timetracker_ui dev docker container. + docker-compose --env-file=.dev.env up -d timetracker_ui + +.PHONY: logs +logs: ## Show logs of timetracker_ui. + docker logs -f timetracker_ui + +.PHONY: stop +stop: ## Stop container timetracker_ui. + docker-compose stop + +.PHONY: restart +restart: ## Restart container timetracker_ui. + docker-compose stop + docker-compose up -d + +.PHONY: remove +remove: ## Delete container timetracker_ui. + docker-compose down --volumes --remove-orphans --rmi local + +.PHONY: test +test: ## Run all tests on docker container timetracker_ui at the CLI. + docker-compose build timetracker_ui_test + docker-compose up -d timetracker_ui_test + docker logs -f timetracker_ui_test + +.PHONY: testdev +testdev: ## Run all tests on docker container timetracker_ui at the Dev + docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d + docker exec timetracker_ui bash -c "npm run ci-test" + +.PHONY: publish +publish: require-image_tag-arg ## Upload a docker image to the stage AWS container registry image_tag= + docker tag timetracker_ui:latest 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/stage-ui:$(image_tag) + docker push 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/stage-ui:$(image_tag) + +.PHONY: build_prod +build_prod: ## Create docker image with dependencies needed for production -- to test locally only + docker build \ + --target production -t timetracker_ui_prod \ + --build-arg API_URL="${API_URL}" \ + --build-arg AUTHORITY="${AUTHORITY}" \ + --build-arg CLIENT_ID="${CLIENT_ID}" \ + --build-arg CLIENT_URL="${CLIENT_URL}" \ + --build-arg SCOPES="${SCOPES}" \ + --build-arg AZURE_APP_CONFIGURATION_CONNECTION_STRING="${AZURE_APP_CONFIGURATION_CONNECTION_STRING}" \ + . + +.PHONY: run_prod +run_prod: ## Execute timetracker_ui_prod docker container -- to test locally only + docker run -d -p 80:80 --name timetracker_ui_prod timetracker_ui_prod + +.PHONY: stop_prod +stop_prod: ## Stop container timetracker_ui_prod. + docker stop timetracker_ui_prod + +.PHONY: remove_prod +remove_prod: ## Delete container timetracker_ui_prod. + docker stop timetracker_ui_prod + docker rm timetracker_ui_prod + +.PHONY: publish_prod +publish_prod: require-image_tag-arg ## Upload a docker image to the prod AWS container registry image_tag= + docker tag timetracker_ui:latest 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/prod-ui:$(image_tag) + docker push 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/prod-ui:$(image_tag) + +.PHONY: login +login: ## Login in respository of docker images + aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 568748651446.dkr.ecr.us-east-1.amazonaws.com + +.PHONY: release +release: require-VERSION-arg require-COMMENT-arg ## Creates an pushes a new tag. + git tag -a ${VERSION} -m "${COMMENT}" + git push origin ${VERSION} + +require-%-arg: + @if [ -z ${${*}} ]; then \ + echo "ERROR: [$*] argument is required, e.g. $*="; \ + exit 1; \ + fi + +require-%-tool: + @if [ "$(shell command -v ${*} 2> /dev/null)" = "" ]; then \ + echo "ERROR: [$*] not found"; \ + exit 1; \ + fi diff --git a/README.md b/README.md index f35dd32dc..71e6e31d8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Time-Tracker-UI +# Time-Tracker-UI v1.50.6 This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 9.0.4. @@ -12,47 +12,110 @@ This project was generated using [Angular CLI](https://github.com/angular/angula ## Prerequisites +In general, you will need to have installed in your machine the following: +- Git +- Visual Studio code(most common) or your favourite editor +- GPG +- Git-crypt + +### Git-crypt + +You can install it by ruuning this command on linux `sudo apt-get install git-crypt`, for mac users `sudo port install git-crypt`. +IMPORTANT: Windows users must install WSL to be able to install git-crypt. + +### GPG + +GPG is a encryption system used to create, import, export public and private keys; you can download it from here: https://gnupg.org/download/index.html. It can only work with a command shell but for Linux users, you can choose to work with Kleopatra if you feel confortable. + ### Node.js -Install Node.js from [https://nodejs.org/en/download/] but we recommend that you install it using Node Version Management [https://github.com/nvm-sh/nvm] (v12.16.1 LTS). +We strongly recommend that you install it using Node Version Management [https://github.com/nvm-sh/nvm] (v12.16.1 LTS) due to some project will only work with a specific version and by using Node Version Management you can switch between versions. ### Angular CLI Angular CLI is a Command Line Interface (CLI) to speed up your development with Angular. -Run `npm install -g @angular/cli` to install Angular CLI +Run `npm install -g @angular/cli` to install Angular CLI. -## Install Node Modules +### Docker + +You can download it from here: https://www.docker.com/get-started/ you will find the perfect Docker version for you. + +### Chocolatey (Only Windows) + +By installing this, you'll be able to use the commands to run your proyect. +You can do it by following the steps here https://chocolatey.org/install. Although the page tells you to use Powershell, you should be able to use any command line with admin permissions. Don't forget to select the "Individual button" before following the installation steps. -Run `npm install` to install the required node_modules for this project. +![image](https://user-images.githubusercontent.com/42116904/166069074-f76d9bd3-01b9-4c50-92e7-c7558d026783.png) -## Development server +### Make (Only Windows) -Run `ng serve` to run the app in dev mode. After executing this command, you can navigate to `http://localhost:4200/` to see the app working. -The app will automatically reload if you change anything in the source files. +You will need to install Make for you to be easier setting your environment. +In your command line with admin permissions run `choco install make` -## Prepare your environment +## Install Node Modules + +In project path, open your favourite command line and run `npm install` in order to be able to run the project locally. + +# Prepare your environment ### Set environment variables -Create a file keys.ts in the path `src/enviroment` with the content pinned in our slack channel: +**1**. Using GPG create your key by running this command in your favourite command shell: `gpg --generate-key`. + +**2**. Export your public key into a file with this command: `gpg --export -a YOUR_REAL_NAME > public.key`. + +**3**. Share your generated key file to your jr techlead. + +**4**. Once your jr techlead added your key to the list, run this command: `git-crypt unlock` to decrypt your environment variables. -``` -export const AUTHORITY = 'XXX'; -export const CLIENT_ID = 'XXX'; -export const SCOPES = ['XXX']; -export const STACK_EXCHANGE_ID = 'XXX'; -export const STACK_EXCHANGE_ACCESS_TOKEN = 'XXX'; -export const AZURE_APP_CONFIGURATION_CONNECTION_STRING = 'XXX'; -``` ### Prepare your environment for vscode -Install the following extensions: +Install the following extensions(optional): -- `EditorConfig for Visual Studio Code`. -- `TSLint` +- `Live Share`. +- `GitLens` - `Prettier - Code formatter`. - Go to user settings (`settings.json`) and enable formatting on save: `"editor.formatOnSave": true`. +## How to run this project + +You have two ways to run this project locally: + +**First (Using Docker)**: + +In your project path, open your favourite command line and run the follwing commands: + +To run the project in development mode: +- `make build` to create a docker image with dependencies needed for development. +- `make run` to execute the development docker container. +- `make logs` to show logs of time-tracker-ui in real time. +- `make stop` to stop the development docker container. + +To run the project in production mode (only for locally testing purposes): +- `make build_prod` to create a docker image with dependencies needed for production. +- `make run_prod` to execute the production docker container. +- `make stop_prod` to stop the production docker container. + +When the project is successfully compiled you can go to `http://localhost:4200/` in your browser. *Remember you must have your Docker running for both cases.* + +**Second (Without using Docker)**: + +Note: If you're on windows, use bash to set up the environment. + +- Set the environment variables executing the following commands: +```bash +set -a +source .env +set +a +``` + +- Run `ng serve` to run the app in development mode. +- Run `ng serve --prod` to run the app in production mode (pointing to the production backend). + +After executing this command, you can navigate to `http://localhost:4200/` to see the app working. This method is usefull when you want to run a specific branch using less time but not recommended when doing QA. + +In any case, the app will automatically reload if you change anything in the source files. + ### Commit messages format Commit messages' format follows the [Conventional Commits guidelines](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification, and specifically we are relying on the [Angular commit specifications](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#-commit-message-guidelines) to bump the [semantic version](https://semver.org/) and generate app change log. @@ -71,7 +134,7 @@ Install the following extensions: - **test**: Adding missing tests or correcting existing tests. ### Example - fix: TT-48 implement semantic versioning + fix: TTA-48 implement semantic versioning Prefix to use in the space fix: `(fix: |feat: |perf: |build: |ci: |docs: |refactor: |style: |test: )` @@ -82,6 +145,12 @@ Install the following extensions: | `feat(pencil): add 'graphiteWidth' option` | ~~Minor~~ Feature Release | | `perf(pencil): remove graphiteWidth option`

`BREAKING CHANGE: The graphiteWidth option has been removed.`
`The default graphite width of 10mm is always used for performance reasons.` | ~~Major~~ Breaking Release | +### Branch names format +For example if your task in Jira is **TTA-48 implement semantic versioning** your branch name is: +``` + TTA-48-implement-semantic-versioning +``` + ## Code scaffolding 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`. @@ -112,7 +181,7 @@ Stryker is also executed on GitHub actions with the following cron expresion: Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -## Deploy the app on Azure +## Deploy the app on Azure (deprecated) The app deployment is automatically executed after each pull request is merged in master. That's wht it is necessary that each pull request meets at least 80% of test coverage. @@ -127,4 +196,7 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C ## Feature Toggles dictionary Shared file with all the Feature Toggles we create, so we can have a history of them -[Feature Toggles dictionary](https://github.com/ioet/time-tracker-ui/wiki/Feature-Toggles-dictionary) \ No newline at end of file +[Feature Toggles dictionary](https://github.com/ioet/time-tracker-ui/wiki/Feature-Toggles-dictionary) + +## More information about the project +[Starting in Time Tracker](https://github.com/ioet/time-tracker-ui/wiki/Time-tracker) diff --git a/angular.json b/angular.json index 4988f5368..586d291cd 100644 --- a/angular.json +++ b/angular.json @@ -11,7 +11,7 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:browser", + "builder": "@angular-builders/custom-webpack:browser", "options": { "outputPath": "dist/time-tracker", "index": "src/index.html", @@ -24,13 +24,15 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "node_modules/datatables.net-dt/css/jquery.dataTables.css", "node_modules/datatables.net-responsive-dt/css/responsive.dataTables.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css", "src/styles.scss", "node_modules/ngx-toastr/toastr.css", "./node_modules/ngx-ui-switch/ui-switch.component.css", - "node_modules/datatables.net-buttons-dt/css/buttons.dataTables.css" + "node_modules/datatables.net-buttons-dt/css/buttons.dataTables.css", + "node_modules/@ng-select/ng-select/themes/default.theme.css" ], "scripts": [ "node_modules/jquery/dist/jquery.js", @@ -43,7 +45,10 @@ "node_modules/datatables.net-buttons/js/buttons.html5.js", "node_modules/datatables.net-buttons/js/buttons.print.js", "node_modules/datatables.net-responsive/js/dataTables.responsive.js" - ] + ], + "customWebpackConfig": { + "path": "webpack.config.js" + } }, "configurations": { "production": { @@ -73,11 +78,39 @@ "maximumError": "10kb" } ] + }, + "productionlegacy": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prodlegacy.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] } } }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/custom-webpack:dev-server", "options": { "browserTarget": "time-tracker:build" }, @@ -94,7 +127,7 @@ } }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular-builders/custom-webpack:karma", "options": { "main": "src/test.ts", "polyfills": "src/polyfills.ts", @@ -106,9 +139,13 @@ "src/assets" ], "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "src/styles.scss" ], - "scripts": [] + "scripts": [], + "customWebpackConfig": { + "path": "webpack.config.js" + } } }, "lint": { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..da0835dd7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +version: '3.9' +services: + timetracker_ui: + container_name: timetracker_ui + image: timetracker_ui + build: + context: . + dockerfile: ./Docker/Dockerfile.dev + ports: + - 4200:4200 + - 9876:9876 + environment: + AUTHORITY: ${AUTHORITY} + API_URL: ${API_URL} + CLIENT_ID: ${CLIENT_ID} + CLIENT_URL: ${CLIENT_URL} + SCOPES: ${SCOPES} + STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} + STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} + AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${AZURE_APP_CONFIGURATION_CONNECTION_STRING} + volumes: + - ./src:/home/timetracker/time-tracker-ui/src/ + - ./scripts:/home/timetracker/time-tracker-ui/scripts/ + - ./e2e:/home/timetracker/time-tracker-ui/e2e/ + - ./coverage:/home/timetracker/time-tracker-ui/coverage + - ./angular.json:/home/timetracker/time-tracker-ui/angular.json + - ./karma.conf.js:/home/timetracker/time-tracker-ui/karma.conf.js + - ./package.json:/home/timetracker/time-tracker-ui/package.json + - ./webpack.config.js:/home/timetracker/time-tracker-ui/webpack.config.js + timetracker_ui_test: + container_name: timetracker_ui_test + image: timetracker_ui_test + build: + context: . + dockerfile: ./Docker/Dockerfile.test + ports: + - 4200:4200 + - 9876:9876 + environment: + CHROME_BIN: /opt/google/chrome/google-chrome + API_URL: ${API_URL} + AUTHORITY: ${AUTHORITY} + CLIENT_ID: ${CLIENT_ID} + CLIENT_URL: ${CLIENT_URL} + SCOPES: ${SCOPES} + STACK_EXCHANGE_ID: ${STACK_EXCHANGE_ID} + STACK_EXCHANGE_ACCESS_TOKEN: ${STACK_EXCHANGE_ACCESS_TOKEN} + AZURE_APP_CONFIGURATION_CONNECTION_STRING: ${AZURE_APP_CONFIGURATION_CONNECTION_STRING} diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js index 7c798cfff..17f6b4754 100644 --- a/e2e/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -13,7 +13,14 @@ exports.config = { './src/**/*.e2e-spec.ts' ], capabilities: { - browserName: 'chrome' + browserName: 'chrome', + 'chromeOptions': { + 'args': [ + '--no-sandbox', + '--headless', + '--window-size=1024,768' + ] + } }, directConnect: true, baseUrl: 'http://localhost:4200/', @@ -29,4 +36,4 @@ exports.config = { }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } -}; \ No newline at end of file +}; diff --git a/infrastructure/aws_ec2.sh b/infrastructure/aws_ec2.sh new file mode 100644 index 000000000..2b9961ed2 --- /dev/null +++ b/infrastructure/aws_ec2.sh @@ -0,0 +1,6 @@ +#!/usr/bin/sh +echo "Deploying $1..." +docker ps -aq | xargs docker stop| xargs docker rm --force --volumes +docker system prune -af +aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 568748651446.dkr.ecr.us-east-1.amazonaws.com +docker run -d --name timetracker_ui --env-file .$2.aws.env -p 80:80 568748651446.dkr.ecr.us-east-1.amazonaws.com/time-tracker/$2-ui:$1 diff --git a/infrastructure/main.tf b/infrastructure/main.tf new file mode 100644 index 000000000..caa878ec7 --- /dev/null +++ b/infrastructure/main.tf @@ -0,0 +1,66 @@ +terraform { + required_version = "~> 1" + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 2.90" + } + aws = { + source = "hashicorp/aws" + version = "~> 4.9.0" + } + } + + backend "s3" { + bucket = "time-tracker-service" + key = "ioet-time-tracker-ui/terraform.tfstate" + region = "us-east-1" + encrypt = true + } +} + +provider "aws" { + region = "us-east-1" +} + +provider "azurerm" { + features {} + skip_provider_registration = true +} + +data "terraform_remote_state" "service" { + backend = "s3" + config = { + bucket = "time-tracker-service" + key = "env://${local.environment}/time-tracker-service/terraform.tfstate" + region = "us-east-1" + } +} + +locals { + common_name = "time-tracker-service" + environment = terraform.workspace + service_name = "${local.common_name}-${local.environment}" + create_app_service_plan = false + service_plan_kind = "Linux" + image_name = "timetracker_ui" +} + +module "ui" { + #source = "../../infra-terraform-modules/azure-app-service" + source = "git@github.com:ioet/infra-terraform-modules.git//azure-app-service?ref=tags/v0.0.20" + app_service_name = "${local.service_name}-ui" + create_app_service_plan = local.create_app_service_plan + docker_image_name = "${local.image_name}:${var.image_tag}" + docker_image_namespace = data.terraform_remote_state.service.outputs.container_registry_login_server + docker_registry_password = data.terraform_remote_state.service.outputs.container_registry_admin_password + docker_registry_url = data.terraform_remote_state.service.outputs.container_registry_login_server + docker_registry_username = data.terraform_remote_state.service.outputs.container_registry_admin_username + location = data.terraform_remote_state.service.outputs.container_registry_location + resource_group_name = data.terraform_remote_state.service.outputs.resource_group_name + service_plan_name = local.service_name + service_plan_size = var.service_plan_size + service_plan_tier = var.service_plan_tier + hostname = "ui" + dns_zone_name = data.terraform_remote_state.service.outputs.subdomain +} diff --git a/infrastructure/prod.tfvars b/infrastructure/prod.tfvars new file mode 100644 index 000000000..d93ddc3de --- /dev/null +++ b/infrastructure/prod.tfvars @@ -0,0 +1,2 @@ +service_plan_size = "S1" +service_plan_tier = "Standard" diff --git a/infrastructure/stage.tfvars b/infrastructure/stage.tfvars new file mode 100644 index 000000000..d93ddc3de --- /dev/null +++ b/infrastructure/stage.tfvars @@ -0,0 +1,2 @@ +service_plan_size = "S1" +service_plan_tier = "Standard" diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf new file mode 100644 index 000000000..6a035a126 --- /dev/null +++ b/infrastructure/variables.tf @@ -0,0 +1,17 @@ +variable "image_tag" { + type = string + description = "Specifies the docker image tag that is stored in a private container registry like ACR (Azure Container Registry)." + sensitive = true +} + +variable "service_plan_size" { + default = "S1" + type = string + description = "Specifies the size of the service plan. This variable format is: Tier (letter) + Size (number). Size could be: 1 = Small (1 Core 1.75GB RAM), 2 = Medium (2 Core 3.5 GB RAM), 3 = Large (4 Core 7GB RAM)" +} + +variable "service_plan_tier" { + default = "Standard" + type = string + description = "Specifies the tier of the service plan. Tier is the pricing plan of the service plan resource." +} diff --git a/karma.conf.js b/karma.conf.js index c2c379965..afb638d5f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,25 +1,32 @@ // 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'], - files: [ - 'node_modules/jquery/dist/jquery.js', - 'node_modules/datatables.net/js/jquery.dataTables.js', - ], + files: ['node_modules/jquery/dist/jquery.js', 'node_modules/datatables.net/js/jquery.dataTables.js'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('@angular-devkit/build-angular/plugins/karma'), require('karma-jasmine-html-reporter'), require('karma-spec-reporter'), - require('karma-coverage-istanbul-reporter') - + require('karma-coverage-istanbul-reporter'), ], client: { - clearContext: false // leave Jasmine Spec Runner output visible in browser + clearContext: false, + jasmine: { + random: true, + seed: '90967', + }, // leave Jasmine Spec Runner output visible in browser + }, + // Karma Typescript compiler options + karmaTypescriptConfig: { + coverageOptions : { + instrumentation: false + } }, coverageIstanbulReporter: { dir: require('path').join(__dirname, './coverage/time-tracker'), @@ -29,8 +36,8 @@ module.exports = function (config) { statements: 80, lines: 80, branches: 80, - functions: 80 - } + functions: 80, + }, }, reporters: ['spec', 'kjhtml'], specReporter: { @@ -39,7 +46,7 @@ module.exports = function (config) { suppressFailed: false, suppressPassed: false, suppressSkipped: true, - showSpecTiming: false + showSpecTiming: false, }, port: 9876, @@ -48,6 +55,9 @@ module.exports = function (config) { autoWatch: true, browsers: ['Chrome'], singleRun: false, - restartOnFileChange: true + restartOnFileChange: true, + proxies: { + '/assets/': '/src/assets/', + }, }); }; diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 000000000..bc375802f --- /dev/null +++ b/nginx.conf @@ -0,0 +1,12 @@ +server { + listen 80; + + root /usr/share/nginx/html; + index index.html; + + server_name _; + + location / { + try_files $uri /index.html; + } +} diff --git a/package-lock.json b/package-lock.json index 31f593150..4da8f3b3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,83 @@ { "name": "time-tracker", - "version": "1.31.16", + "version": "2.6.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@angular-builders/custom-webpack": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-10.0.1.tgz", + "integrity": "sha512-YDy5zEKVwXdoXLjmbsY6kGaEbmunQxaPipxrwLUc9hIjRLU2WcrX9vopf1R9Pgj4POad73IPBNGu+ibqNRFIEQ==", + "dev": true, + "requires": { + "@angular-devkit/architect": ">=0.1000.0 < 0.1100.0", + "@angular-devkit/build-angular": ">=0.1000.0 < 0.1100.0", + "@angular-devkit/core": "^10.0.0", + "lodash": "^4.17.15", + "ts-node": "^9.0.0", + "webpack-merge": "^4.2.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1002.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1002.3.tgz", + "integrity": "sha512-7ainXRNO1njZ6bBbJXGpMzCh0OYrzuIRe/+zRj0ncV1YfEsJb2yWBuiza0+y2Ljco7hdd4wr+7eJm7cfn+NvAw==", + "dev": true, + "requires": { + "@angular-devkit/core": "10.2.3", + "rxjs": "6.6.2" + } + }, + "@angular-devkit/core": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-10.2.3.tgz", + "integrity": "sha512-pMM1v9Xjqx6YLOQxQYs0D+03H6XPDZLS8cyEtoQX2iYdh8qlKHZVbJa2WsfzwMoIPtgcXfQAXn113VEgrQPLFA==", + "dev": true, + "requires": { + "ajv": "6.12.4", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.2", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "rxjs": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", + "integrity": "sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "ts-node": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.1.1.tgz", + "integrity": "sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + } + } + } + }, "@angular-devkit/architect": { "version": "0.901.12", "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.12.tgz", @@ -161,6 +235,15 @@ "uri-js": "^4.2.2" } }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "cacache": { "version": "15.0.5", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", @@ -186,12 +269,49 @@ "unique-filename": "^1.1.1" } }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -201,6 +321,12 @@ "minipass": "^3.0.0" } }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, "minipass": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", @@ -236,6 +362,25 @@ "is-wsl": "^2.1.1" } }, + "postcss": { + "version": "7.0.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", + "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "rxjs": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.2.tgz", @@ -252,18 +397,27 @@ "dev": true }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" } }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -417,6 +571,28 @@ } } }, + "@angular/cdk": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.2.3.tgz", + "integrity": "sha512-QehBMt36nNfXZ8nBGJLLUQlexzQv6rohQmEYXULHarAC3Ily8DnB9wDGJ4emlKyGz1MIVYR0tZP39RmQp0hH+g==", + "requires": { + "parse5": "^5.0.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "optional": true + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + } + } + }, "@angular/cli": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-10.2.0.tgz", @@ -562,12 +738,13 @@ } }, "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "npm-pick-manifest": { @@ -841,6 +1018,36 @@ "integrity": "sha512-j7EhGAiOfbfnCXrEqWkGThA1E4oN6eETKc6SP80Upvtp/K9OU4EtvcFIcALY4YBBg5Yjs5F3gtTlv0ZHK03AsA==", "dev": true }, + "@angular/material": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-11.2.3.tgz", + "integrity": "sha512-esIDzEmShc+fhkbgLn4kYcmc855NShR2YuqyPHs7fxRXOlKkK9c2PHKjhk+Vd8Hf7gtilJwuCB4kReKMl0psWQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } + } + }, + "@angular/material-moment-adapter": { + "version": "11.2.9", + "resolved": "https://registry.npmjs.org/@angular/material-moment-adapter/-/material-moment-adapter-11.2.9.tgz", + "integrity": "sha512-qeZKzNIywqVfgmSj34wBBfuh/YIqLnICj3LFqSA2/odrxcBDFBKF6iu+R3GDNlTqivIoor1eyaODxB+jD2shSA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } + } + }, "@angular/platform-browser": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-10.2.2.tgz", @@ -886,6 +1093,21 @@ } } }, + "@auth0/angular-jwt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.0.2.tgz", + "integrity": "sha512-rSamC9mu+gUxoR86AXcIo+KD7xRIro+/iu1F2Ld85YAZEVKlpB5vYG+g0yGaEOqjtQWP/i0H6fi6XMGPVHSYYQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + } + } + }, "@azure/abort-controller": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.1.tgz", @@ -2578,6 +2800,31 @@ "schema-utils": "^2.7.0" } }, + "@mattlewis92/dom-autoscroller": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@mattlewis92/dom-autoscroller/-/dom-autoscroller-2.4.2.tgz", + "integrity": "sha512-YbrUWREPGEjE/FU6foXcAT1YbVwqD/jkYnY1dFb0o4AxtP3s4xKBthlELjndZih8uwsDWgQZx1eNskRNe2BgZQ==" + }, + "@ng-select/ng-select": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-7.2.0.tgz", + "integrity": "sha512-o4A8DAIV36lOy3xzIE0cYH5psACcIYDOfU3XzQQ+/WCKO1ChNH0cXUWtOAI1B/AF1yW/NpADxPPbFcrknAuYaA==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "@ngneat/tailwind": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@ngneat/tailwind/-/tailwind-5.2.5.tgz", + "integrity": "sha512-4S8RMcJP67XT8JxOtAumer3J2W34aG11ynXr2M4pvakcRidytDsS3kxJ42RHl7aRgdvfS2uUCoIdCixQTR4JMw==" + }, "@ngrx/effects": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-10.0.1.tgz", @@ -2714,35 +2961,36 @@ } }, "@octokit/auth-token": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz", - "integrity": "sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz", + "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==", "dev": true, "requires": { - "@octokit/types": "^6.0.0" + "@octokit/types": "^6.0.3" } }, "@octokit/core": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz", - "integrity": "sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.4.0.tgz", + "integrity": "sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==", "dev": true, "requires": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", "@octokit/request": "^5.4.12", + "@octokit/request-error": "^2.0.5", "@octokit/types": "^6.0.3", - "before-after-hook": "^2.1.0", + "before-after-hook": "^2.2.0", "universal-user-agent": "^6.0.0" } }, "@octokit/endpoint": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.10.tgz", - "integrity": "sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz", + "integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==", "dev": true, "requires": { - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", "universal-user-agent": "^6.0.0" }, @@ -2756,56 +3004,56 @@ } }, "@octokit/graphql": { - "version": "4.5.8", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.8.tgz", - "integrity": "sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA==", + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.1.tgz", + "integrity": "sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==", "dev": true, "requires": { "@octokit/request": "^5.3.0", - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "universal-user-agent": "^6.0.0" } }, "@octokit/openapi-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.0.0.tgz", - "integrity": "sha512-J4bfM7lf8oZvEAdpS71oTvC1ofKxfEZgU5vKVwzZKi4QPiL82udjpseJwxPid9Pu2FNmyRQOX4iEj6W1iOSnPw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz", + "integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==", "dev": true }, "@octokit/plugin-paginate-rest": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.6.2.tgz", - "integrity": "sha512-3Dy7/YZAwdOaRpGQoNHPeT0VU1fYLpIUdPyvR37IyFLgd6XSij4j9V/xN/+eSjF2KKvmfIulEh9LF1tRPjIiDA==", + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz", + "integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==", "dev": true, "requires": { - "@octokit/types": "^6.0.1" + "@octokit/types": "^6.11.0" } }, "@octokit/plugin-request-log": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz", - "integrity": "sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz", + "integrity": "sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==", "dev": true }, "@octokit/plugin-rest-endpoint-methods": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.1.tgz", - "integrity": "sha512-+v5PcvrUcDeFXf8hv1gnNvNLdm4C0+2EiuWt9EatjjUmfriM1pTMM+r4j1lLHxeBQ9bVDmbywb11e3KjuavieA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz", + "integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==", "dev": true, "requires": { - "@octokit/types": "^6.1.0", + "@octokit/types": "^6.13.0", "deprecation": "^2.3.1" } }, "@octokit/request": { - "version": "5.4.12", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.12.tgz", - "integrity": "sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz", + "integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==", "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.0.0", - "@octokit/types": "^6.0.3", + "@octokit/types": "^6.7.1", "deprecation": "^2.0.0", "is-plain-object": "^5.0.0", "node-fetch": "^2.6.1", @@ -2822,36 +3070,35 @@ } }, "@octokit/request-error": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.4.tgz", - "integrity": "sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz", + "integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==", "dev": true, "requires": { - "@octokit/types": "^6.0.0", + "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "@octokit/rest": { - "version": "18.0.12", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.12.tgz", - "integrity": "sha512-hNRCZfKPpeaIjOVuNJzkEL6zacfZlBPV8vw8ReNeyUkVvbuCvvrrx8K8Gw2eyHHsmd4dPlAxIXIZ9oHhJfkJpw==", + "version": "18.5.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz", + "integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==", "dev": true, "requires": { "@octokit/core": "^3.2.3", "@octokit/plugin-paginate-rest": "^2.6.2", "@octokit/plugin-request-log": "^1.0.2", - "@octokit/plugin-rest-endpoint-methods": "4.4.1" + "@octokit/plugin-rest-endpoint-methods": "5.0.0" } }, "@octokit/types": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.1.1.tgz", - "integrity": "sha512-btm3D6S7VkRrgyYF31etUtVY/eQ1KzrNRqhFt25KSe2mKlXuLXJilglRC6eDA2P6ou94BUnk/Kz5MPEolXgoiw==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz", + "integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==", "dev": true, "requires": { - "@octokit/openapi-types": "^2.0.0", - "@types/node": ">= 8" + "@octokit/openapi-types": "^6.0.0" } }, "@opencensus/web-types": { @@ -2872,6 +3119,11 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.6.1.tgz", "integrity": "sha512-5bHhlTBBq82ti3qPT15TRxkYTFPPQWbnkkQkmHPtqiS1XcTB69cEKd3Jm7Cfi/vkPoyxapmePE9tyA7EzLt8SQ==" }, + "@scarf/scarf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.1.1.tgz", + "integrity": "sha512-VGbKDbk1RFIaSmdVb0cNjjWJoRWRI/Weo23AjRCC2nryO0iAS8pzsToJfPVPtVs74WHw4L1UTADNdIYRLkirZQ==" + }, "@schematics/angular": { "version": "9.1.12", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.12.tgz", @@ -3093,9 +3345,9 @@ } }, "@semantic-release/github": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.2.0.tgz", - "integrity": "sha512-tMRnWiiWb43whRHvbDGXq4DGEbKRi56glDpXDJZit4PIiwDPX7Kx3QzmwRtDOcG+8lcpGjpdPabYZ9NBxoI2mw==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-7.2.1.tgz", + "integrity": "sha512-+gOhbaG4T3xJb6aTZu1/7KvCmYKRChkasdIyFWdaGaTWVeGpdl4o0zMviV1z3kRcgPOSXeqjHSQ6SOQAfHQiDw==", "dev": true, "requires": { "@octokit/rest": "^18.0.0", @@ -3126,15 +3378,15 @@ } }, "fs-extra": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", - "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "requires": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", - "universalify": "^1.0.0" + "universalify": "^2.0.0" } }, "http-proxy-agent": { @@ -3166,26 +3418,18 @@ "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" - }, - "dependencies": { - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - } } }, "mime": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", - "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true }, "p-retry": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", - "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.5.0.tgz", + "integrity": "sha512-5Hwh4aVQSu6BEP+w2zKlVXtFAaYQe1qWuVADSgoeVlLjwe/Q/AMSoRR4MDeaAfu8llT+YNbEijWu/YF3m6avkg==", "dev": true, "requires": { "@types/retry": "^0.12.0", @@ -3199,17 +3443,17 @@ "dev": true }, "universalify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", - "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true } } }, "@semantic-release/npm": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.6.tgz", - "integrity": "sha512-F4judxdeLe8f7+vDva1TkqNc5Tb2tcltZYW0tLtvP2Xt7CD/gGiz7UxAWEOPsXBvIqAP+uTidvGLPl9U3/uRoQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-7.0.5.tgz", + "integrity": "sha512-D+oEmsx9aHE1q806NFQwSC9KdBO8ri/VO99eEz0wWbX2jyLqVyWr7t0IjKC8aSnkkQswg/4KN/ZjfF6iz1XOpw==", "dev": true, "requires": { "@semantic-release/error": "^2.2.0", @@ -3219,7 +3463,7 @@ "lodash": "^4.17.15", "nerf-dart": "^1.0.0", "normalize-url": "^5.0.0", - "npm": "^6.13.0", + "npm": "^6.10.3", "rc": "^1.2.8", "read-pkg": "^5.0.0", "registry-auth-token": "^4.0.0", @@ -3283,13 +3527,21 @@ "dev": true }, "jsonfile": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.0.1.tgz", - "integrity": "sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", - "universalify": "^1.0.0" + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } } }, "normalize-url": { @@ -3346,9 +3598,9 @@ } }, "@semantic-release/release-notes-generator": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.1.tgz", - "integrity": "sha512-bOoTiH6SiiR0x2uywSNR7uZcRDl22IpZhj+Q5Bn0v+98MFtOMhCxFhbrKQjhbYoZw7vps1mvMRmFkp/g6R9cvQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-9.0.2.tgz", + "integrity": "sha512-xGFSidhGqB27uwgWCU6y0gbf4r/no5flOAkJyFFc4+bPf8S+LfAVm7xhhlK5VPXLt2Iu1RBH8F+IgMK2ah5YpA==", "dev": true, "requires": { "conventional-changelog-angular": "^5.0.0", @@ -3358,7 +3610,7 @@ "debug": "^4.0.0", "get-stream": "^5.0.0", "import-from": "^3.0.0", - "into-stream": "^5.0.0", + "into-stream": "^6.0.0", "lodash": "^4.17.4", "read-pkg-up": "^7.0.0" }, @@ -3389,6 +3641,12 @@ } } }, + "@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", + "dev": true + }, "@stryker-mutator/api": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-3.3.1.tgz", @@ -3580,6 +3838,27 @@ "integrity": "sha512-JE9PT6/Fqo4343fQWb3YzbkGmBU4bUeuynYhsTeDmY0fhTT653oGHeyEKWeV39/kgSyKfIpY5ABb1RvJclOgkg==", "dev": true }, + "@tailwindcss/forms": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.3.3.tgz", + "integrity": "sha512-U8Fi/gq4mSuaLyLtFISwuDYzPB73YzgozjxOIHsK6NXgg/IWD1FLaHbFlWmurAMyy98O+ao74ksdQefsquBV1Q==", + "dev": true, + "requires": { + "mini-svg-data-uri": "^1.2.3" + } + }, + "@tailwindcss/typography": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.4.1.tgz", + "integrity": "sha512-ovPPLUhs7zAIJfr0y1dbGlyCuPhpuv/jpBoFgqAc658DWGGrOBWBMpAWLw2KlzbNeVk4YBJMzue1ekvIbdw6XA==", + "dev": true, + "requires": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3591,6 +3870,24 @@ "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", + "dev": true + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, "@types/datatables.net": { "version": "1.10.19", "resolved": "https://registry.npmjs.org/@types/datatables.net/-/datatables.net-1.10.19.tgz", @@ -3619,24 +3916,24 @@ } }, "@types/jasmine": { - "version": "3.5.14", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.14.tgz", - "integrity": "sha512-Fkgk536sHPqcOtd+Ow+WiUNuk0TSo/BntKkF8wSvcd6M2FvPjeXcUE6Oz/bwDZiUZEaXLslAgw00Q94Pnx6T4w==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.0.tgz", + "integrity": "sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==", "dev": true }, "@types/jasminewd2": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.8.tgz", - "integrity": "sha512-d9p31r7Nxk0ZH0U39PTH0hiDlJ+qNVGjlt1ucOoTUptxb2v+Y5VMnsxfwN+i3hK4yQnqBi3FMmoMFcd1JHDxdg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz", + "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==", "dev": true, "requires": { "@types/jasmine": "*" } }, "@types/jquery": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz", - "integrity": "sha512-Tyctjh56U7eX2b9udu3wG853ASYP0uagChJcQJXLUXEU6C/JiW5qt5dl8ao01VRj1i5pgXPAf8f1mq4+FDLRQg==", + "version": "3.3.38", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.38.tgz", + "integrity": "sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA==", "requires": { "@types/sizzle": "*" } @@ -3665,9 +3962,9 @@ "dev": true }, "@types/node": { - "version": "12.12.62", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.62.tgz", - "integrity": "sha512-qAfo81CsD7yQIM9mVyh6B/U47li5g7cfpVQEDMfQeF8pSZVwzbhwU3crc0qG4DmpsebpJPR49AKOExQyJ05Cpg==" + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.11.1.tgz", + "integrity": "sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==" }, "@types/node-fetch": { "version": "2.5.7", @@ -3981,6 +4278,31 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, "adjust-sourcemap-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", @@ -3997,12 +4319,6 @@ "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", "dev": true }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", - "dev": true - }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -4058,6 +4374,26 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, + "angular-calendar": { + "version": "0.28.26", + "resolved": "https://registry.npmjs.org/angular-calendar/-/angular-calendar-0.28.26.tgz", + "integrity": "sha512-rb4CBowC7tp+gUL2n1mfPw5XBXJW1w1uhXlMRTqQlC9LQfWT37tjt+Ve5jXrTq/f6qBs226H6o9AFCjc5/5O/g==", + "requires": { + "@scarf/scarf": "^1.1.0", + "angular-draggable-droppable": "^4.6.0", + "angular-resizable-element": "^3.3.5", + "calendar-utils": "^0.8.4", + "positioning": "^2.0.1", + "tslib": "^1.14.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "angular-datatables": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/angular-datatables/-/angular-datatables-9.0.2.tgz", @@ -4100,12 +4436,44 @@ } } }, + "angular-draggable-droppable": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/angular-draggable-droppable/-/angular-draggable-droppable-4.6.0.tgz", + "integrity": "sha512-+8JhTDMKkc/NuFFqb8/H/QHpB+v4Z7YNrgvEbV+PQxXry19rkr89ofZgjNIXhZexTvJNb03BYlSQoknzXE9b3g==", + "requires": { + "@mattlewis92/dom-autoscroller": "^2.4.2", + "tslib": "^1.9.0" + } + }, "angular-ng-autocomplete": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/angular-ng-autocomplete/-/angular-ng-autocomplete-2.0.1.tgz", "integrity": "sha512-IlKrUeMM6V0/ipnlXF5WluiWmav7eiIlZWtEoch6eXUGylYCHGNdwRO4Kb2snMQuY/6Kv6tDbRFT51Tihd3JwQ==", "dev": true }, + "angular-resizable-element": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/angular-resizable-element/-/angular-resizable-element-3.4.0.tgz", + "integrity": "sha512-xL5a8FmghzrZmHPy7uwWz98m91gRXgAcdeCRYcK/nD7psXMTYNk5EPmHA0qZTDCIYljhT4h0OKWLvx56NQGfDA==", + "requires": { + "tslib": "^1.9.0" + } + }, + "angularx-social-login": { + "version": "3.5.7", + "resolved": "https://registry.npmjs.org/angularx-social-login/-/angularx-social-login-3.5.7.tgz", + "integrity": "sha512-KhkRzQph1mQt1BNsVhWhI2FDSo0y31sbhlFOVtJrLaaub/vhiNVB4NPeOXU7oQmiTkqKMWibp8pSOEJ92EQTbw==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -4270,12 +4638,6 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", - "dev": true - }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -4410,6 +4772,86 @@ "num2fraction": "^1.2.2", "postcss": "^7.0.32", "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "aws-sign2": { @@ -4486,12 +4928,6 @@ "object.assign": "^4.1.0" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -4552,12 +4988,6 @@ } } }, - "base64-arraybuffer": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", - "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=", - "dev": true - }, "base64-js": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", @@ -4584,9 +5014,9 @@ } }, "before-after-hook": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", - "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.1.tgz", + "integrity": "sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw==", "dev": true }, "big.js": { @@ -4601,6 +5031,16 @@ "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bl": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz", @@ -4635,12 +5075,6 @@ } } }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", - "dev": true - }, "blocking-proxy": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz", @@ -4738,9 +5172,9 @@ "dev": true }, "bootstrap": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.2.tgz", - "integrity": "sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" }, "bottleneck": { "version": "2.19.5", @@ -4867,15 +5301,42 @@ } }, "browserslist": { - "version": "4.14.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz", - "integrity": "sha512-zeFYcUo85ENhc/zxHbiIp0LGzzTrE2Pv2JhxvS7kpUb9Q9D38kUX6Bie7pGutJ/5iF5rOxE7CepAuWD56xJ33A==", + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001154", - "electron-to-chromium": "^1.3.585", + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", "escalade": "^3.1.1", - "node-releases": "^1.1.65" + "node-releases": "^1.1.71" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30001230", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz", + "integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.739", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz", + "integrity": "sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + } } }, "browserstack": { @@ -5005,6 +5466,11 @@ "unset-value": "^1.0.0" } }, + "calendar-utils": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/calendar-utils/-/calendar-utils-0.8.4.tgz", + "integrity": "sha512-0btakIxxHrCMX/7L6jayixKG9OnGIbqAh5oHe0Me2LYAS7zdEUoRxndyN4N04yVyZURgx1iNXj4l5m51/sDShg==" + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -5035,6 +5501,12 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, "camelcase-keys": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", @@ -5194,64 +5666,19 @@ "integrity": "sha512-sJAofoarcm76ZGpuooaO0eDy8saEy+YoZBLjC4h8srt4jeBnkYeOgqxgsJQTpyt2LjI5PTfLJHSL+41Yu4fEJA==" }, "cli-table": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", - "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", + "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", "dev": true, "requires": { - "chalk": "^2.4.1", - "string-width": "^4.2.0" + "colors": "1.0.3" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, @@ -5467,9 +5894,9 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-string": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", - "integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", "dev": true, "requires": { "color-name": "^1.0.0", @@ -5579,24 +6006,12 @@ "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", "dev": true }, - "component-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", - "dev": true - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", "dev": true }, - "component-inherit": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", - "dev": true - }, "compose-function": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz", @@ -5756,9 +6171,9 @@ } }, "conventional-changelog-writer": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.18.tgz", - "integrity": "sha512-mAQDCKyB9HsE8Ko5cCM1Jn1AWxXPYV0v8dFPabZRkvsiWUul2YyAqbIaoMKF88Zf2ffnOPSvKhboLf3fnjo5/A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.1.0.tgz", + "integrity": "sha512-WwKcUp7WyXYGQmkLsX4QmU42AZ1lqlvRW9mqoyiQzdD+rJWbTepdWoKJuwXTS+yq79XKnQNa93/roViPQrAQgw==", "dev": true, "requires": { "compare-func": "^2.0.0", @@ -5784,18 +6199,18 @@ } }, "hosted-git-info": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.7.tgz", - "integrity": "sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, "meow": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-8.0.0.tgz", - "integrity": "sha512-nbsTRz2fwniJBFgUkcdISq8y/q9n9VbiHYbfwklFh5V4V2uAcxtKQkDc0yCLPM/kP0d+inZBewn3zJqewHE7kg==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", "dev": true, "requires": { "@types/minimist": "^1.2.0", @@ -5812,21 +6227,21 @@ } }, "normalize-package-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.0.tgz", - "integrity": "sha512-6lUjEI0d3v6kFrtgA/lOx4zHCWULXsFNIjHolnZCKCTLA6m/G625cdn3O7eNmT0iD3jfo6HZ9cdImGZwf21prw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.2.tgz", + "integrity": "sha512-6CdZocmfGaKnIHPVFhJJZ3GuR8SsLKvDANFp47Jmy51aKIr8akjAWTSxtpI+MBgBFdSMRyo4hMpDlT6dTffgZg==", "dev": true, "requires": { - "hosted-git-info": "^3.0.6", - "resolve": "^1.17.0", - "semver": "^7.3.2", + "hosted-git-info": "^4.0.1", + "resolve": "^1.20.0", + "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" }, "dependencies": { "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -5845,6 +6260,16 @@ "util-deprecate": "^1.0.1" } }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -5867,9 +6292,9 @@ "dev": true }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true } } @@ -6072,18 +6497,18 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" } }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -6125,6 +6550,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", @@ -6182,6 +6617,12 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -6262,39 +6703,197 @@ "requires": { "postcss": "^7.0.1", "timsort": "^0.3.0" - } - }, - "css-loader": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.2.2.tgz", - "integrity": "sha512-omVGsTkZPVwVRpckeUnLshPp12KsmMSLqYxs12+RzM9jRR5Y+Idn/tBffjXRvOE+qW7if24cuceFJqYR5FmGBg==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^2.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.3", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^7.3.2" }, "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-loader": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.2.2.tgz", + "integrity": "sha512-omVGsTkZPVwVRpckeUnLshPp12KsmMSLqYxs12+RzM9jRR5Y+Idn/tBffjXRvOE+qW7if24cuceFJqYR5FmGBg==", + "dev": true, + "requires": { + "camelcase": "^6.0.0", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^2.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.3", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^7.3.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "camelcase": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", + "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } }, "semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -6353,6 +6952,12 @@ } } }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", + "dev": true + }, "css-what": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", @@ -6384,186 +6989,436 @@ "cssnano-preset-default": "^4.0.7", "is-resolvable": "^1.0.0", "postcss": "^7.0.0" - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "dev": true, - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", - "dev": true - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", - "dev": true - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", - "dev": true - }, - "csso": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.1.0.tgz", - "integrity": "sha512-h+6w/W1WqXaJA4tb1dk7r5tVbOm97MsKxzwnvOR04UQ6GILroryjMWu3pmCCtL2mLaEStQ0fZgeGiy99mo7iyg==", - "dev": true, - "requires": { - "css-tree": "^1.0.0" }, "dependencies": { - "css-tree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0.tgz", - "integrity": "sha512-CdVYz/Yuqw0VdKhXPBIgi8DO3NicJVYZNWeX9XcIuSp9ZoFT5IcleVRW07O5rMjdcx1mb+MEJPknTTEW7DdsYw==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "mdn-data": "2.0.12", - "source-map": "^0.6.1" + "color-convert": "^1.9.0" } }, - "mdn-data": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", - "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", "dev": true, "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", "dev": true, "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "datatables.net": { - "version": "1.10.22", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.22.tgz", - "integrity": "sha512-ujn8GvkQIBYzYH54XY7OrI0Zb35TKRd9ABYfbnXgBfwTGIFT6UsmXrfHU5Yk+MSDoF0sDu2TB+31V6c+zUZ0Pw==", - "requires": { - "jquery": ">=1.7" - } - }, - "datatables.net-buttons": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-1.6.4.tgz", - "integrity": "sha512-KxiQs1UdczsT26sptLE9P550G9QFPdwcAO9o57NHIME/lfNp9qLxL0ho0mmjQfugEBrJxqff7xGkPpK6CyKDRw==", - "requires": { - "datatables.net": "^1.10.15", - "jquery": ">=1.7" - } - }, - "datatables.net-buttons-dt": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/datatables.net-buttons-dt/-/datatables.net-buttons-dt-1.6.4.tgz", - "integrity": "sha512-vpZgElAeaOpzIrb4XE0YoWgO2lLr67hVp+/hDw0/QhOhtP9nZidkclnlSIL8yxDjv3CLaKrVXuC8FtGbNF9kbw==", - "requires": { - "datatables.net-buttons": "1.6.4", - "datatables.net-dt": "^1.10.15", - "jquery": ">=1.7" - } - }, - "datatables.net-dt": { - "version": "1.10.22", - "resolved": "https://registry.npmjs.org/datatables.net-dt/-/datatables.net-dt-1.10.22.tgz", - "integrity": "sha512-JYqE8noge7YGOydsiyLsWtPUofOHFEMCeOwS47kpykWqDJyU6GE2E3JaEeDq6hokg39UQKDNIRFnsM7GhA/BGw==", + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.1.0.tgz", + "integrity": "sha512-h+6w/W1WqXaJA4tb1dk7r5tVbOm97MsKxzwnvOR04UQ6GILroryjMWu3pmCCtL2mLaEStQ0fZgeGiy99mo7iyg==", + "dev": true, + "requires": { + "css-tree": "^1.0.0" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0.tgz", + "integrity": "sha512-CdVYz/Yuqw0VdKhXPBIgi8DO3NicJVYZNWeX9XcIuSp9ZoFT5IcleVRW07O5rMjdcx1mb+MEJPknTTEW7DdsYw==", + "dev": true, + "requires": { + "mdn-data": "2.0.12", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.12.tgz", + "integrity": "sha512-ULbAlgzVb8IqZ0Hsxm6hHSlQl3Jckst2YEQS7fODu9ilNWy2LvcoSY7TRFIktABP2mdppBioc66va90T+NUs8Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", + "dev": true + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "datatables.net": { + "version": "1.10.22", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.22.tgz", + "integrity": "sha512-ujn8GvkQIBYzYH54XY7OrI0Zb35TKRd9ABYfbnXgBfwTGIFT6UsmXrfHU5Yk+MSDoF0sDu2TB+31V6c+zUZ0Pw==", + "requires": { + "jquery": ">=1.7" + } + }, + "datatables.net-buttons": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/datatables.net-buttons/-/datatables.net-buttons-1.6.2.tgz", + "integrity": "sha512-0sKWBBwtCCECRc4T+fUDhFCJv9miLPrB90QsVYq4ep0hIMuWiW76+eCdykZq5yd/3m7K1O+ryt9c7PahBDf91g==", + "requires": { + "datatables.net": "^1.10.15", + "jquery": ">=1.7" + } + }, + "datatables.net-buttons-dt": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/datatables.net-buttons-dt/-/datatables.net-buttons-dt-1.6.2.tgz", + "integrity": "sha512-CEEhXLqLwJP17RktzdZBN142dvILUj0OnlFtCO1hl4YjFz8ttYGc6Qfi/OhU2GcH0z9sW2bcYKbvAZe2/Uiazg==", + "requires": { + "datatables.net-buttons": "1.6.2", + "datatables.net-dt": "^1.10.15", + "jquery": ">=1.7" + } + }, + "datatables.net-dt": { + "version": "1.10.21", + "resolved": "https://registry.npmjs.org/datatables.net-dt/-/datatables.net-dt-1.10.21.tgz", + "integrity": "sha512-P89PgkhVCB6shP0CbigmB1Z812yfdfhvAbUdg08mLuF7tuvLFPQC9F+WIV30Hj9mgMsPALWAEJEYAA3aJjuqIA==", "requires": { - "datatables.net": "1.10.22", + "datatables.net": "1.10.21", "jquery": ">=1.7" + }, + "dependencies": { + "datatables.net": { + "version": "1.10.21", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.21.tgz", + "integrity": "sha512-/bSZtxmf3GTpYcvEmwZ8q26I1yhSx8qklR2B+s1K8+/51UW/zc2zTYwJMqr/Z+iCYixAc00ildj4g2x0Qamolw==", + "requires": { + "jquery": ">=1.7" + } + } } }, "datatables.net-responsive": { @@ -6585,6 +7440,11 @@ "jquery": ">=1.7" } }, + "date-fns": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.22.1.tgz", + "integrity": "sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==" + }, "date-format": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", @@ -6668,6 +7528,11 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deepmerge": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", + "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==" + }, "default-gateway": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", @@ -6744,6 +7609,12 @@ } } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, "del": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", @@ -6762,7 +7633,7 @@ "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", "dev": true, "requires": { "array-uniq": "^1.0.1" @@ -6863,6 +7734,17 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "dev": true, + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", @@ -6878,6 +7760,12 @@ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", "dev": true }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -6912,6 +7800,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -6919,9 +7813,9 @@ "dev": true }, "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", "dev": true, "requires": { "ip": "^1.1.0", @@ -6998,6 +7892,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-14.2.0.tgz", + "integrity": "sha512-05POuPJyPpO6jqzTNweQFfAyMSD4qa4lvsMOWyTRTdpHKy6nnnN+IYWaXF+lHivhBH/ufDKlR4IWCAN3oPnHuw==" + }, "duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -7041,31 +7940,25 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "dev": true }, - "electron-to-chromium": { - "version": "1.3.591", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.591.tgz", - "integrity": "sha512-ol/0WzjL4NS4Kqy9VD6xXQON91xIihDT36sYCew/G/bnd1v0/4D+kahp26JauQhgFUjrdva3kRSo7URcUmQ+qw==", - "dev": true - }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", "dev": true } } @@ -7103,17 +7996,54 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.3.tgz", + "integrity": "sha512-rqs60YwkvWTLLnfazqgZqLa/aKo+9cueVfEi/dZ8PyGyaf8TLOxj++4QMIgeG3Gn0AhrWiFXvghsoY9L9h25GA==", + "dev": true, + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "dev": true + } + } + }, "engine.io-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", - "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", "dev": true, "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.5", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "@socket.io/base64-arraybuffer": "~1.0.2" } }, "enhanced-resolve": { @@ -7781,6 +8711,13 @@ "schema-utils": "^2.6.5" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "file-url": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/file-url/-/file-url-3.0.0.tgz", @@ -7955,9 +8892,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "version": "1.14.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", + "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "dev": true }, "for-in": { @@ -8264,9 +9201,9 @@ "dev": true }, "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", "dev": true, "requires": { "minimist": "^1.2.5", @@ -8329,29 +9266,6 @@ } } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "dev": true, - "requires": { - "isarray": "2.0.1" - }, - "dependencies": { - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - } - } - }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8478,9 +9392,9 @@ "dev": true }, "hosted-git-info": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.5.tgz", - "integrity": "sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz", + "integrity": "sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==", "requires": { "lru-cache": "^6.0.0" } @@ -8527,6 +9441,12 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "dev": true + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", @@ -8761,15 +9681,15 @@ } }, "husky": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz", - "integrity": "sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.3.tgz", + "integrity": "sha512-VxTsSTRwYveKXN4SaH1/FefRJYCtx+wx04sSVcOpD7N2zjoHxa+cEJ07Qg5NmV3HAK+IRKOyNVpi2YBIVccIfQ==", "dev": true, "requires": { - "chalk": "^4.0.0", + "chalk": "^3.0.0", "ci-info": "^2.0.0", - "compare-versions": "^3.6.0", - "cosmiconfig": "^7.0.0", + "compare-versions": "^3.5.1", + "cosmiconfig": "^6.0.0", "find-versions": "^3.2.0", "opencollective-postinstall": "^2.0.2", "pkg-dir": "^4.2.0", @@ -8778,27 +9698,17 @@ "which-pm-runs": "^1.0.0" }, "dependencies": { - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "cosmiconfig": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", - "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", + "import-fresh": "^3.1.0", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.10.0" + "yaml": "^1.7.2" } }, "find-up": { @@ -8812,9 +9722,9 @@ } }, "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -8840,9 +9750,9 @@ } }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -8889,6 +9799,86 @@ "dev": true, "requires": { "postcss": "^7.0.14" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "ieee754": { @@ -8982,12 +9972,6 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, - "indexof": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", - "dev": true - }, "infer-owner": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", @@ -9043,9 +10027,9 @@ } }, "into-stream": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", - "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", "dev": true, "requires": { "from2": "^2.3.0", @@ -9150,6 +10134,15 @@ "rgba-regex": "^1.0.0" } }, + "is-core-module": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", + "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -9343,6 +10336,12 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -9363,9 +10362,9 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isbinaryfile": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", - "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", "dev": true }, "isexe": { @@ -9713,9 +10712,9 @@ } }, "jszip": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.5.0.tgz", - "integrity": "sha512-WRtu7TPCmYePR1nazfrtuF216cIVon/3GWOvHS9QR5bIwSbnxtdpma6un3jyGGNhHsKCSzn5Ypk+EkDRvTGiFA==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.0.tgz", + "integrity": "sha512-Y2OlFIzrDOPWUnpU0LORIcDn2xN7rC9yKffFM/7pGhQuhO+SUhfm2trkJ/S5amjFvem0Y+1EALz/MEPkvHXVNw==", "requires": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -9743,85 +10742,184 @@ } }, "karma": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/karma/-/karma-5.2.2.tgz", - "integrity": "sha512-rB3Ua5yDxmIupTj67r3Q8itz7TxJzRE6DmVcOfV20D509Uu9AoBKlVwbZhND4kcm6BqLfbHtv4DZC9QJfrUY+w==", + "version": "6.3.16", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz", + "integrity": "sha512-nEU50jLvDe5yvXqkEJRf8IuvddUkOY2x5Xc4WXHz6dxINgGDrgD2uqQWeVrJs4hbfNaotn+HQ1LZJ4yOXrL7xQ==", "dev": true, "requires": { "body-parser": "^1.19.0", "braces": "^3.0.2", - "chokidar": "^3.4.2", - "colors": "^1.4.0", + "chokidar": "^3.5.1", + "colors": "1.4.0", "connect": "^3.7.0", "di": "^0.0.1", "dom-serialize": "^2.2.1", - "glob": "^7.1.6", - "graceful-fs": "^4.2.4", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.6", - "lodash": "^4.17.19", - "log4js": "^6.2.1", - "mime": "^2.4.5", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", "qjobs": "^1.2.0", "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "socket.io": "^2.3.0", + "socket.io": "^4.2.0", "source-map": "^0.6.1", - "tmp": "0.2.1", - "ua-parser-js": "0.7.21", - "yargs": "^15.3.1" + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" }, "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "date-format": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.4.tgz", + "integrity": "sha512-/jyf4rhB17ge328HJuJjAcmRtCsGd+NDeAtahRBTaK6vSPR6MO5HlrAit3Nn7dVjaa6sowW0WXt8yQtLyZQFRg==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "ms": "2.1.2" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "fs-extra": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", + "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "log4js": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.2.tgz", + "integrity": "sha512-k80cggS2sZQLBwllpT1p06GtfvzMmSdUCkW96f0Hj83rKGJDAu2vZjt9B9ag2vx8Zz1IXzxoLgqvRJCdMKybGg==", + "dev": true, + "requires": { + "date-format": "^4.0.4", + "debug": "^4.3.3", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.0.4" } }, "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "picomatch": "^2.2.1" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, "source-map": { @@ -9830,6 +10928,17 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, + "streamroller": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.4.tgz", + "integrity": "sha512-GI9NzeD+D88UFuIlJkKNDH/IsuR+qIN7Qh8EsmhoRZr9bQoehTraRgwtLUkZbpcAw+hLPfHOypmppz8YyGK68w==", + "dev": true, + "requires": { + "date-format": "^4.0.4", + "debug": "^4.3.3", + "fs-extra": "^10.0.1" + } + }, "tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", @@ -9839,10 +10948,16 @@ "rimraf": "^3.0.0" } }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -9850,34 +10965,32 @@ "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, @@ -9891,9 +11004,9 @@ } }, "karma-coverage-istanbul-reporter": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz", - "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz", + "integrity": "sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==", "dev": true, "requires": { "istanbul-api": "^2.1.6", @@ -9910,9 +11023,9 @@ } }, "karma-jasmine-html-reporter": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.5.4.tgz", - "integrity": "sha512-PtilRLno5O6wH3lDihRnz0Ba8oSn0YUJqKjjux1peoYGwo0AQqrWRbdWk/RLzcGlb+onTyXAnHl6M+Hu3UxG/Q==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.4.2.tgz", + "integrity": "sha512-7g0gPj8+9JepCNJR9WjDyQ2RkZ375jpdurYQyAYv8PorUCadepl8vrD6LmMqOGcM17cnrynBawQYZHaumgDjBw==", "dev": true }, "karma-json-fixtures-preprocessor": { @@ -10045,6 +11158,12 @@ "immediate": "~3.0.5" } }, + "lilconfig": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.3.tgz", + "integrity": "sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -10099,9 +11218,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.capitalize": { "version": "4.2.1", @@ -10109,6 +11228,12 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, + "lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=", + "dev": true + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -10157,6 +11282,12 @@ "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -10169,6 +11300,12 @@ "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", "dev": true }, + "lodash.topath": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.topath/-/lodash.topath-4.5.2.tgz", + "integrity": "sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -10363,20 +11500,20 @@ } }, "marked": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.7.tgz", - "integrity": "sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.1.tgz", + "integrity": "sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw==", "dev": true }, "marked-terminal": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.0.tgz", - "integrity": "sha512-5KllfAOW02WS6hLRQ7cNvGOxvKW1BKuXELH4EtbWfyWgxQhROoMxEvuQ/3fTgkNjledR0J48F4HbapvYp1zWkQ==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-4.1.1.tgz", + "integrity": "sha512-t7Mdf6T3PvOEyN01c3tYxDzhyKZ8xnkp8Rs6Fohno63L/0pFTJ5Qtwto2AQVuDtbQiWzD+4E5AAu1Z2iLc8miQ==", "dev": true, "requires": { "ansi-escapes": "^4.3.1", "cardinal": "^2.1.1", - "chalk": "^4.0.0", + "chalk": "^4.1.0", "cli-table": "^0.3.1", "node-emoji": "^1.10.0", "supports-hyperlinks": "^2.1.0" @@ -10626,6 +11763,12 @@ } } }, + "mini-svg-data-uri": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz", + "integrity": "sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -10798,6 +11941,12 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "optional": true }, + "modern-normalize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modern-normalize/-/modern-normalize-1.1.0.tgz", + "integrity": "sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==", + "dev": true + }, "modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -10805,9 +11954,9 @@ "dev": true }, "moment": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.28.0.tgz", - "integrity": "sha512-Z5KOjYmnHyd/ukynmFd/WwyXHd7L4J9vTI/nn5Ap9AVUgaAE15VvQ9MOGmJJygEUklupqIrFnor/tjTwRU+tQw==" + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz", + "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==" }, "move-concurrently": { "version": "1.0.1", @@ -10838,9 +11987,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "msal": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/msal/-/msal-1.4.0.tgz", - "integrity": "sha512-NTxMFQh6t5g2QWMlvZTWTxL1bmcqiCv0cs2lxTHhUbWEuxWCfvaVRZfjxN8i+T0VltVVGaVIdML8QEoBnlbaSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/msal/-/msal-1.2.1.tgz", + "integrity": "sha512-Zo28eyRtT/Un+zcpMfPtTPD+eo/OqzsRER0k5dyk8Mje/K1oLlaEOAgZHlJs59Y2xyuVg8OrcKqSn/1MeNjZYw==", "requires": { "tslib": "^1.9.3" } @@ -10893,6 +12042,12 @@ "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "optional": true }, + "nanoid": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -10949,6 +12104,22 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "ngrx-store-localstorage": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ngrx-store-localstorage/-/ngrx-store-localstorage-11.0.0.tgz", + "integrity": "sha512-/bLA9/0orlEffBGH4Nqp5XwdgFX9XkpBD5p2ZwRYPPYAHi+bs4yFdKl6c4RvKsS9IOGHXiO5mm2vLaK+n7mvTw==", + "requires": { + "deepmerge": "^3.2.0", + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==" + } + } + }, "ngx-cookie-service": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-11.0.2.tgz", @@ -10965,11 +12136,18 @@ } }, "ngx-mask": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-9.1.4.tgz", - "integrity": "sha512-CaegSUqdH6ajSapKD55uBLo6cRroMYNrCFk7rQCVRv8XPNLHsddG1CBV/9MWFrWOdnQ5eSBZ6zJQG+eimKzifw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-12.0.0.tgz", + "integrity": "sha512-q4vUjhjJfg4faRud/tUdCTOs3JA6B+rBB2OPZ2xBZy4LNTRKGfUK683LrDCitMVBezjEAVrkQdUT1I4C7LXBZQ==", "requires": { - "tslib": "^1.10.0" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } } }, "ngx-material-timepicker": { @@ -10988,12 +12166,9 @@ "integrity": "sha512-Ur0pTWRe2ZXoJ8impEzo0IZKxY5aEcQfSmL5uBqW1rHI2J6nfzgZAHsSLagKHFGchXq0PkRlDVVMcIaNxYJwvQ==" }, "ngx-toastr": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-12.1.0.tgz", - "integrity": "sha512-rytCRBhvuudj614Kfj9GoIVQDrFuLvHSMP1YrMwTmR1SNkNJZOpGKmaSDCCBrNDkSrGouzMWBlFbl1UDBBsiqw==", - "requires": { - "tslib": "^1.10.0" - } + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-12.0.1.tgz", + "integrity": "sha512-PABtbn2dyHweVSbo/py1W3veXzcmZO7uVItfTW9AykSSeAUju3gOCgauAw89km0aJ9EBcPOieaoI+9tAR7Pfug==" }, "ngx-ui-switch": { "version": "10.0.2", @@ -11033,9 +12208,33 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } }, "node-fetch-npm": { "version": "2.0.4", @@ -11086,12 +12285,6 @@ } } }, - "node-releases": { - "version": "1.1.66", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz", - "integrity": "sha512-JHEQ1iWPGK+38VLB2H9ef2otU4l8s3yAMt9Xf934r6+ojCYDMHPMqvCc9TnzfeFSP1QEOeU6YZEd3+De0LTCgg==", - "dev": true - }, "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", @@ -11110,9 +12303,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "semver": { "version": "5.7.1", @@ -11272,8 +12465,7 @@ "dependencies": { "JSONStream": { "version": "1.3.5", - "resolved": false, - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "bundled": true, "dev": true, "requires": { "jsonparse": "^1.2.0", @@ -11282,14 +12474,12 @@ }, "abbrev": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "dev": true }, "agent-base": { "version": "4.3.0", - "resolved": false, - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "bundled": true, "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -11297,8 +12487,7 @@ }, "agentkeepalive": { "version": "3.5.2", - "resolved": false, - "integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==", + "bundled": true, "dev": true, "requires": { "humanize-ms": "^1.2.1" @@ -11306,8 +12495,7 @@ }, "ajv": { "version": "5.5.2", - "resolved": false, - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "bundled": true, "dev": true, "requires": { "co": "^4.6.0", @@ -11318,8 +12506,7 @@ }, "ansi-align": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "bundled": true, "dev": true, "requires": { "string-width": "^2.0.0" @@ -11327,14 +12514,12 @@ }, "ansi-regex": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "bundled": true, "dev": true }, "ansi-styles": { "version": "3.2.1", - "resolved": false, - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "bundled": true, "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11342,32 +12527,27 @@ }, "ansicolors": { "version": "0.3.2", - "resolved": false, - "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "bundled": true, "dev": true }, "ansistyles": { "version": "0.1.3", - "resolved": false, - "integrity": "sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=", + "bundled": true, "dev": true }, "aproba": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "bundled": true, "dev": true }, "archy": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "bundled": true, "dev": true }, "are-we-there-yet": { "version": "1.1.4", - "resolved": false, - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "bundled": true, "dev": true, "requires": { "delegates": "^1.0.0", @@ -11376,8 +12556,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -11391,8 +12570,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -11402,14 +12580,12 @@ }, "asap": { "version": "2.0.6", - "resolved": false, - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "bundled": true, "dev": true }, "asn1": { "version": "0.2.4", - "resolved": false, - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "bundled": true, "dev": true, "requires": { "safer-buffer": "~2.1.0" @@ -11417,38 +12593,32 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "bundled": true, "dev": true }, "asynckit": { "version": "0.4.0", - "resolved": false, - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "bundled": true, "dev": true }, "aws-sign2": { "version": "0.7.0", - "resolved": false, - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "bundled": true, "dev": true }, "aws4": { "version": "1.8.0", - "resolved": false, - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "bundled": true, "dev": true }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "bundled": true, "dev": true }, "bcrypt-pbkdf": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -11457,8 +12627,7 @@ }, "bin-links": { "version": "1.1.8", - "resolved": false, - "integrity": "sha512-KgmVfx+QqggqP9dA3iIc5pA4T1qEEEL+hOhOhNPaUm77OTrJoOXE/C05SJLNJe6m/2wUK7F1tDSou7n5TfCDzQ==", + "bundled": true, "dev": true, "requires": { "bluebird": "^3.5.3", @@ -11471,14 +12640,12 @@ }, "bluebird": { "version": "3.5.5", - "resolved": false, - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "bundled": true, "dev": true }, "boxen": { "version": "1.3.0", - "resolved": false, - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "bundled": true, "dev": true, "requires": { "ansi-align": "^2.0.0", @@ -11492,8 +12659,7 @@ }, "brace-expansion": { "version": "1.1.11", - "resolved": false, - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -11502,32 +12668,27 @@ }, "buffer-from": { "version": "1.0.0", - "resolved": false, - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "bundled": true, "dev": true }, "builtins": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", + "bundled": true, "dev": true }, "byline": { "version": "5.0.0", - "resolved": false, - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=", + "bundled": true, "dev": true }, "byte-size": { "version": "5.0.1", - "resolved": false, - "integrity": "sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==", + "bundled": true, "dev": true }, "cacache": { "version": "12.0.3", - "resolved": false, - "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", + "bundled": true, "dev": true, "requires": { "bluebird": "^3.5.5", @@ -11549,32 +12710,27 @@ }, "call-limit": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-5twvci5b9eRBw2wCfPtN0GmlR2/gadZqyFpPhOK6CvMFoFgA+USnZ6Jpu1lhG9h85pQ3Ouil3PfXWRD4EUaRiQ==", + "bundled": true, "dev": true }, "camelcase": { "version": "4.1.0", - "resolved": false, - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "bundled": true, "dev": true }, "capture-stack-trace": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", + "bundled": true, "dev": true }, "caseless": { "version": "0.12.0", - "resolved": false, - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "bundled": true, "dev": true }, "chalk": { "version": "2.4.1", - "resolved": false, - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "bundled": true, "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -11584,20 +12740,17 @@ }, "chownr": { "version": "1.1.4", - "resolved": false, - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "bundled": true, "dev": true }, "ci-info": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "bundled": true, "dev": true }, "cidr-regex": { "version": "2.0.10", - "resolved": false, - "integrity": "sha512-sB3ogMQXWvreNPbJUZMRApxuRYd+KoIo4RGQ81VatjmMW6WJPo+IJZ2846FGItr9VzKo5w7DXzijPLGtSd0N3Q==", + "bundled": true, "dev": true, "requires": { "ip-regex": "^2.1.0" @@ -11605,14 +12758,12 @@ }, "cli-boxes": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "bundled": true, "dev": true }, "cli-columns": { "version": "3.1.2", - "resolved": false, - "integrity": "sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=", + "bundled": true, "dev": true, "requires": { "string-width": "^2.0.0", @@ -11621,8 +12772,7 @@ }, "cli-table3": { "version": "0.5.1", - "resolved": false, - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", + "bundled": true, "dev": true, "requires": { "colors": "^1.1.2", @@ -11632,8 +12782,7 @@ }, "cliui": { "version": "5.0.0", - "resolved": false, - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "bundled": true, "dev": true, "requires": { "string-width": "^3.1.0", @@ -11643,20 +12792,17 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "bundled": true, "dev": true }, "string-width": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "bundled": true, "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -11666,8 +12812,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": false, - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -11677,14 +12822,12 @@ }, "clone": { "version": "1.0.4", - "resolved": false, - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "bundled": true, "dev": true }, "cmd-shim": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-DtGg+0xiFhQIntSBRzL2fRQBnmtAVwXIDo4Qq46HPpObYquxMaZS4sb82U9nH91qJrlosC1wa9gwr0QyL/HypA==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -11693,20 +12836,17 @@ }, "co": { "version": "4.6.0", - "resolved": false, - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "bundled": true, "dev": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "bundled": true, "dev": true }, "color-convert": { "version": "1.9.1", - "resolved": false, - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "bundled": true, "dev": true, "requires": { "color-name": "^1.1.1" @@ -11714,21 +12854,18 @@ }, "color-name": { "version": "1.1.3", - "resolved": false, - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "bundled": true, "dev": true }, "colors": { "version": "1.3.3", - "resolved": false, - "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "bundled": true, "dev": true, "optional": true }, "columnify": { "version": "1.5.4", - "resolved": false, - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", + "bundled": true, "dev": true, "requires": { "strip-ansi": "^3.0.0", @@ -11737,8 +12874,7 @@ }, "combined-stream": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "bundled": true, "dev": true, "requires": { "delayed-stream": "~1.0.0" @@ -11746,14 +12882,12 @@ }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "bundled": true, "dev": true }, "concat-stream": { "version": "1.6.2", - "resolved": false, - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "bundled": true, "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -11764,8 +12898,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -11779,8 +12912,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -11790,8 +12922,7 @@ }, "config-chain": { "version": "1.1.12", - "resolved": false, - "integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==", + "bundled": true, "dev": true, "requires": { "ini": "^1.3.4", @@ -11800,8 +12931,7 @@ }, "configstore": { "version": "3.1.5", - "resolved": false, - "integrity": "sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==", + "bundled": true, "dev": true, "requires": { "dot-prop": "^4.2.1", @@ -11814,14 +12944,12 @@ }, "console-control-strings": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "bundled": true, "dev": true }, "copy-concurrently": { "version": "1.0.5", - "resolved": false, - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "bundled": true, "dev": true, "requires": { "aproba": "^1.1.1", @@ -11834,28 +12962,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true }, "iferr": { "version": "0.1.5", - "resolved": false, - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "bundled": true, "dev": true } } }, "core-util-is": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "dev": true }, "create-error-class": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "bundled": true, "dev": true, "requires": { "capture-stack-trace": "^1.0.0" @@ -11863,8 +12987,7 @@ }, "cross-spawn": { "version": "5.1.0", - "resolved": false, - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "bundled": true, "dev": true, "requires": { "lru-cache": "^4.0.1", @@ -11874,8 +12997,7 @@ "dependencies": { "lru-cache": { "version": "4.1.5", - "resolved": false, - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "bundled": true, "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -11884,28 +13006,24 @@ }, "yallist": { "version": "2.1.2", - "resolved": false, - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "bundled": true, "dev": true } } }, "crypto-random-string": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "bundled": true, "dev": true }, "cyclist": { "version": "0.2.2", - "resolved": false, - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "bundled": true, "dev": true }, "dashdash": { "version": "1.14.1", - "resolved": false, - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -11913,8 +13031,7 @@ }, "debug": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "bundled": true, "dev": true, "requires": { "ms": "2.0.0" @@ -11922,40 +13039,34 @@ "dependencies": { "ms": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "bundled": true, "dev": true } } }, "debuglog": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "bundled": true, "dev": true }, "decamelize": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "bundled": true, "dev": true }, "decode-uri-component": { "version": "0.2.0", - "resolved": false, - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "bundled": true, "dev": true }, "deep-extend": { "version": "0.6.0", - "resolved": false, - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "bundled": true, "dev": true }, "defaults": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "bundled": true, "dev": true, "requires": { "clone": "^1.0.2" @@ -11963,8 +13074,7 @@ }, "define-properties": { "version": "1.1.3", - "resolved": false, - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "bundled": true, "dev": true, "requires": { "object-keys": "^1.0.12" @@ -11972,32 +13082,27 @@ }, "delayed-stream": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "bundled": true, "dev": true }, "delegates": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "dev": true }, "detect-indent": { "version": "5.0.0", - "resolved": false, - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "bundled": true, "dev": true }, "detect-newline": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "bundled": true, "dev": true }, "dezalgo": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "bundled": true, "dev": true, "requires": { "asap": "^2.0.0", @@ -12006,8 +13111,7 @@ }, "dot-prop": { "version": "4.2.1", - "resolved": false, - "integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==", + "bundled": true, "dev": true, "requires": { "is-obj": "^1.0.0" @@ -12015,20 +13119,17 @@ }, "dotenv": { "version": "5.0.1", - "resolved": false, - "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==", + "bundled": true, "dev": true }, "duplexer3": { "version": "0.1.4", - "resolved": false, - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "bundled": true, "dev": true }, "duplexify": { "version": "3.6.0", - "resolved": false, - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "bundled": true, "dev": true, "requires": { "end-of-stream": "^1.0.0", @@ -12039,8 +13140,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12054,8 +13154,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -12065,8 +13164,7 @@ }, "ecc-jsbn": { "version": "0.1.2", - "resolved": false, - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "bundled": true, "dev": true, "optional": true, "requires": { @@ -12076,20 +13174,17 @@ }, "editor": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=", + "bundled": true, "dev": true }, "emoji-regex": { "version": "7.0.3", - "resolved": false, - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "bundled": true, "dev": true }, "encoding": { "version": "0.1.12", - "resolved": false, - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", + "bundled": true, "dev": true, "requires": { "iconv-lite": "~0.4.13" @@ -12097,8 +13192,7 @@ }, "end-of-stream": { "version": "1.4.1", - "resolved": false, - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "bundled": true, "dev": true, "requires": { "once": "^1.4.0" @@ -12106,20 +13200,17 @@ }, "env-paths": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "bundled": true, "dev": true }, "err-code": { "version": "1.1.2", - "resolved": false, - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=", + "bundled": true, "dev": true }, "errno": { "version": "0.1.7", - "resolved": false, - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "bundled": true, "dev": true, "requires": { "prr": "~1.0.1" @@ -12127,8 +13218,7 @@ }, "es-abstract": { "version": "1.12.0", - "resolved": false, - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", + "bundled": true, "dev": true, "requires": { "es-to-primitive": "^1.1.1", @@ -12140,8 +13230,7 @@ }, "es-to-primitive": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "bundled": true, "dev": true, "requires": { "is-callable": "^1.1.4", @@ -12151,14 +13240,12 @@ }, "es6-promise": { "version": "4.2.8", - "resolved": false, - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "bundled": true, "dev": true }, "es6-promisify": { "version": "5.0.0", - "resolved": false, - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "bundled": true, "dev": true, "requires": { "es6-promise": "^4.0.3" @@ -12166,14 +13253,12 @@ }, "escape-string-regexp": { "version": "1.0.5", - "resolved": false, - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "bundled": true, "dev": true }, "execa": { "version": "0.7.0", - "resolved": false, - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "bundled": true, "dev": true, "requires": { "cross-spawn": "^5.0.1", @@ -12187,52 +13272,44 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "bundled": true, "dev": true } } }, "extend": { "version": "3.0.2", - "resolved": false, - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "bundled": true, "dev": true }, "extsprintf": { "version": "1.3.0", - "resolved": false, - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "bundled": true, "dev": true }, "fast-deep-equal": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "bundled": true, "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "bundled": true, "dev": true }, "figgy-pudding": { "version": "3.5.1", - "resolved": false, - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "bundled": true, "dev": true }, "find-npm-prefix": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==", + "bundled": true, "dev": true }, "flush-write-stream": { "version": "1.0.3", - "resolved": false, - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "bundled": true, "dev": true, "requires": { "inherits": "^2.0.1", @@ -12241,8 +13318,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12256,8 +13332,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -12267,14 +13342,12 @@ }, "forever-agent": { "version": "0.6.1", - "resolved": false, - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "bundled": true, "dev": true }, "form-data": { "version": "2.3.2", - "resolved": false, - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "bundled": true, "dev": true, "requires": { "asynckit": "^0.4.0", @@ -12284,8 +13357,7 @@ }, "from2": { "version": "2.3.0", - "resolved": false, - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "bundled": true, "dev": true, "requires": { "inherits": "^2.0.1", @@ -12294,8 +13366,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12309,8 +13380,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -12320,8 +13390,7 @@ }, "fs-minipass": { "version": "1.2.7", - "resolved": false, - "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "bundled": true, "dev": true, "requires": { "minipass": "^2.6.0" @@ -12329,8 +13398,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -12341,8 +13409,7 @@ }, "fs-vacuum": { "version": "1.2.10", - "resolved": false, - "integrity": "sha1-t2Kb7AekAxolSP35n17PHMizHjY=", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -12352,8 +13419,7 @@ }, "fs-write-stream-atomic": { "version": "1.0.10", - "resolved": false, - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -12364,14 +13430,12 @@ "dependencies": { "iferr": { "version": "0.1.5", - "resolved": false, - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "bundled": true, "dev": true }, "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -12385,8 +13449,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -12396,20 +13459,17 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "dev": true }, "function-bind": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "bundled": true, "dev": true }, "gauge": { "version": "2.7.4", - "resolved": false, - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "dev": true, "requires": { "aproba": "^1.0.3", @@ -12424,14 +13484,12 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true }, "string-width": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -12443,14 +13501,12 @@ }, "genfun": { "version": "5.0.0", - "resolved": false, - "integrity": "sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==", + "bundled": true, "dev": true }, "gentle-fs": { "version": "2.3.1", - "resolved": false, - "integrity": "sha512-OlwBBwqCFPcjm33rF2BjW+Pr6/ll2741l+xooiwTCeaX2CA1ZuclavyMBe0/KlR21/XGsgY6hzEQZ15BdNa13Q==", + "bundled": true, "dev": true, "requires": { "aproba": "^1.1.2", @@ -12468,28 +13524,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true }, "iferr": { "version": "0.1.5", - "resolved": false, - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "bundled": true, "dev": true } } }, "get-caller-file": { "version": "2.0.5", - "resolved": false, - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "bundled": true, "dev": true }, "get-stream": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "bundled": true, "dev": true, "requires": { "pump": "^3.0.0" @@ -12497,8 +13549,7 @@ }, "getpass": { "version": "0.1.7", - "resolved": false, - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0" @@ -12506,8 +13557,7 @@ }, "glob": { "version": "7.1.6", - "resolved": false, - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "bundled": true, "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -12520,8 +13570,7 @@ }, "global-dirs": { "version": "0.1.1", - "resolved": false, - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "bundled": true, "dev": true, "requires": { "ini": "^1.3.4" @@ -12529,8 +13578,7 @@ }, "got": { "version": "6.7.1", - "resolved": false, - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "bundled": true, "dev": true, "requires": { "create-error-class": "^3.0.0", @@ -12548,28 +13596,24 @@ "dependencies": { "get-stream": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "bundled": true, "dev": true } } }, "graceful-fs": { "version": "4.2.4", - "resolved": false, - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "bundled": true, "dev": true }, "har-schema": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "bundled": true, "dev": true }, "har-validator": { "version": "5.1.0", - "resolved": false, - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "bundled": true, "dev": true, "requires": { "ajv": "^5.3.0", @@ -12578,8 +13622,7 @@ }, "has": { "version": "1.0.3", - "resolved": false, - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "bundled": true, "dev": true, "requires": { "function-bind": "^1.1.1" @@ -12587,38 +13630,32 @@ }, "has-flag": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "bundled": true, "dev": true }, "has-symbols": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "bundled": true, "dev": true }, "has-unicode": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "dev": true }, "hosted-git-info": { "version": "2.8.8", - "resolved": false, - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "bundled": true, "dev": true }, "http-cache-semantics": { "version": "3.8.1", - "resolved": false, - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "bundled": true, "dev": true }, "http-proxy-agent": { "version": "2.1.0", - "resolved": false, - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", + "bundled": true, "dev": true, "requires": { "agent-base": "4", @@ -12627,8 +13664,7 @@ }, "http-signature": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -12638,8 +13674,7 @@ }, "https-proxy-agent": { "version": "2.2.4", - "resolved": false, - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "bundled": true, "dev": true, "requires": { "agent-base": "^4.3.0", @@ -12648,8 +13683,7 @@ }, "humanize-ms": { "version": "1.2.1", - "resolved": false, - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "bundled": true, "dev": true, "requires": { "ms": "^2.0.0" @@ -12657,8 +13691,7 @@ }, "iconv-lite": { "version": "0.4.23", - "resolved": false, - "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "bundled": true, "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -12666,14 +13699,12 @@ }, "iferr": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==", + "bundled": true, "dev": true }, "ignore-walk": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "bundled": true, "dev": true, "requires": { "minimatch": "^3.0.4" @@ -12681,26 +13712,22 @@ }, "import-lazy": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "bundled": true, "dev": true }, "imurmurhash": { "version": "0.1.4", - "resolved": false, - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "bundled": true, "dev": true }, "infer-owner": { "version": "1.0.4", - "resolved": false, - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "bundled": true, "dev": true }, "inflight": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "dev": true, "requires": { "once": "^1.3.0", @@ -12709,20 +13736,17 @@ }, "inherits": { "version": "2.0.4", - "resolved": false, - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "bundled": true, "dev": true }, "ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "version": "1.3.5", + "bundled": true, "dev": true }, "init-package-json": { "version": "1.10.3", - "resolved": false, - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", + "bundled": true, "dev": true, "requires": { "glob": "^7.1.1", @@ -12737,26 +13761,22 @@ }, "ip": { "version": "1.1.5", - "resolved": false, - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "bundled": true, "dev": true }, "ip-regex": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "bundled": true, "dev": true }, "is-callable": { "version": "1.1.4", - "resolved": false, - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "bundled": true, "dev": true }, "is-ci": { "version": "1.2.1", - "resolved": false, - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", + "bundled": true, "dev": true, "requires": { "ci-info": "^1.5.0" @@ -12764,16 +13784,14 @@ "dependencies": { "ci-info": { "version": "1.6.0", - "resolved": false, - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", + "bundled": true, "dev": true } } }, "is-cidr": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-8Xnnbjsb0x462VoYiGlhEi+drY8SFwrHiSYuzc/CEwco55vkehTaxAyIjEdpi3EMvLPPJAJi9FlzP+h+03gp0Q==", + "bundled": true, "dev": true, "requires": { "cidr-regex": "^2.0.10" @@ -12781,14 +13799,12 @@ }, "is-date-object": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "bundled": true, "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -12796,8 +13812,7 @@ }, "is-installed-globally": { "version": "0.1.0", - "resolved": false, - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "bundled": true, "dev": true, "requires": { "global-dirs": "^0.1.0", @@ -12806,20 +13821,17 @@ }, "is-npm": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "bundled": true, "dev": true }, "is-obj": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "bundled": true, "dev": true }, "is-path-inside": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "bundled": true, "dev": true, "requires": { "path-is-inside": "^1.0.1" @@ -12827,14 +13839,12 @@ }, "is-redirect": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", + "bundled": true, "dev": true }, "is-regex": { "version": "1.0.4", - "resolved": false, - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "bundled": true, "dev": true, "requires": { "has": "^1.0.1" @@ -12842,20 +13852,17 @@ }, "is-retry-allowed": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "bundled": true, "dev": true }, "is-stream": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "bundled": true, "dev": true }, "is-symbol": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "bundled": true, "dev": true, "requires": { "has-symbols": "^1.0.0" @@ -12863,69 +13870,58 @@ }, "is-typedarray": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "bundled": true, "dev": true }, "isarray": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "dev": true }, "isexe": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "bundled": true, "dev": true }, "isstream": { "version": "0.1.2", - "resolved": false, - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "bundled": true, "dev": true }, "jsbn": { "version": "0.1.1", - "resolved": false, - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "bundled": true, "dev": true, "optional": true }, "json-parse-better-errors": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "bundled": true, "dev": true }, "json-schema": { "version": "0.2.3", - "resolved": false, - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "bundled": true, "dev": true }, "json-schema-traverse": { "version": "0.3.1", - "resolved": false, - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "bundled": true, "dev": true }, "json-stringify-safe": { "version": "5.0.1", - "resolved": false, - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "bundled": true, "dev": true }, "jsonparse": { "version": "1.3.1", - "resolved": false, - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "bundled": true, "dev": true }, "jsprim": { "version": "1.4.1", - "resolved": false, - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "bundled": true, "dev": true, "requires": { "assert-plus": "1.0.0", @@ -12936,8 +13932,7 @@ }, "latest-version": { "version": "3.1.0", - "resolved": false, - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "bundled": true, "dev": true, "requires": { "package-json": "^4.0.0" @@ -12945,14 +13940,12 @@ }, "lazy-property": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=", + "bundled": true, "dev": true }, "libcipm": { "version": "4.0.8", - "resolved": false, - "integrity": "sha512-IN3hh2yDJQtZZ5paSV4fbvJg4aHxCCg5tcZID/dSVlTuUiWktsgaldVljJv6Z5OUlYspx6xQkbR0efNodnIrOA==", + "bundled": true, "dev": true, "requires": { "bin-links": "^1.1.2", @@ -12974,8 +13967,7 @@ }, "libnpm": { "version": "3.0.1", - "resolved": false, - "integrity": "sha512-d7jU5ZcMiTfBqTUJVZ3xid44fE5ERBm9vBnmhp2ECD2Ls+FNXWxHSkO7gtvrnbLO78gwPdNPz1HpsF3W4rjkBQ==", + "bundled": true, "dev": true, "requires": { "bin-links": "^1.1.2", @@ -13002,8 +13994,7 @@ }, "libnpmaccess": { "version": "3.0.2", - "resolved": false, - "integrity": "sha512-01512AK7MqByrI2mfC7h5j8N9V4I7MHJuk9buo8Gv+5QgThpOgpjB7sQBDDkeZqRteFb1QM/6YNdHfG7cDvfAQ==", + "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", @@ -13014,8 +14005,7 @@ }, "libnpmconfig": { "version": "1.2.1", - "resolved": false, - "integrity": "sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==", + "bundled": true, "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -13025,8 +14015,7 @@ "dependencies": { "find-up": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "bundled": true, "dev": true, "requires": { "locate-path": "^3.0.0" @@ -13034,8 +14023,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "bundled": true, "dev": true, "requires": { "p-locate": "^3.0.0", @@ -13044,8 +14032,7 @@ }, "p-limit": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "bundled": true, "dev": true, "requires": { "p-try": "^2.0.0" @@ -13053,8 +14040,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "bundled": true, "dev": true, "requires": { "p-limit": "^2.0.0" @@ -13062,16 +14048,14 @@ }, "p-try": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "bundled": true, "dev": true } } }, "libnpmhook": { "version": "5.0.3", - "resolved": false, - "integrity": "sha512-UdNLMuefVZra/wbnBXECZPefHMGsVDTq5zaM/LgKNE9Keyl5YXQTnGAzEo+nFOpdRqTWI9LYi4ApqF9uVCCtuA==", + "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", @@ -13082,8 +14066,7 @@ }, "libnpmorg": { "version": "1.0.1", - "resolved": false, - "integrity": "sha512-0sRUXLh+PLBgZmARvthhYXQAWn0fOsa6T5l3JSe2n9vKG/lCVK4nuG7pDsa7uMq+uTt2epdPK+a2g6btcY11Ww==", + "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", @@ -13094,8 +14077,7 @@ }, "libnpmpublish": { "version": "1.1.2", - "resolved": false, - "integrity": "sha512-2yIwaXrhTTcF7bkJKIKmaCV9wZOALf/gsTDxVSu/Gu/6wiG3fA8ce8YKstiWKTxSFNC0R7isPUb6tXTVFZHt2g==", + "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", @@ -13111,8 +14093,7 @@ }, "libnpmsearch": { "version": "2.0.2", - "resolved": false, - "integrity": "sha512-VTBbV55Q6fRzTdzziYCr64+f8AopQ1YZ+BdPOv16UegIEaE8C0Kch01wo4s3kRTFV64P121WZJwgmBwrq68zYg==", + "bundled": true, "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -13122,8 +14103,7 @@ }, "libnpmteam": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-p420vM28Us04NAcg1rzgGW63LMM6rwe+6rtZpfDxCcXxM0zUTLl7nPFEnRF3JfFBF5skF/yuZDUthTsHgde8QA==", + "bundled": true, "dev": true, "requires": { "aproba": "^2.0.0", @@ -13134,8 +14114,7 @@ }, "libnpx": { "version": "10.2.4", - "resolved": false, - "integrity": "sha512-BPc0D1cOjBeS8VIBKUu5F80s6njm0wbVt7CsGMrIcJ+SI7pi7V0uVPGpEMH9H5L8csOcclTxAXFE2VAsJXUhfA==", + "bundled": true, "dev": true, "requires": { "dotenv": "^5.0.1", @@ -13150,8 +14129,7 @@ }, "lock-verify": { "version": "2.1.0", - "resolved": false, - "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==", + "bundled": true, "dev": true, "requires": { "npm-package-arg": "^6.1.0", @@ -13160,8 +14138,7 @@ }, "lockfile": { "version": "1.0.4", - "resolved": false, - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", + "bundled": true, "dev": true, "requires": { "signal-exit": "^3.0.2" @@ -13169,14 +14146,12 @@ }, "lodash._baseindexof": { "version": "3.1.0", - "resolved": false, - "integrity": "sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw=", + "bundled": true, "dev": true }, "lodash._baseuniq": { "version": "4.6.0", - "resolved": false, - "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "bundled": true, "dev": true, "requires": { "lodash._createset": "~4.0.0", @@ -13185,20 +14160,17 @@ }, "lodash._bindcallback": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "bundled": true, "dev": true }, "lodash._cacheindexof": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI=", + "bundled": true, "dev": true }, "lodash._createcache": { "version": "3.1.2", - "resolved": false, - "integrity": "sha1-VtagZAF2JeeevKa4AY4XRAvc8JM=", + "bundled": true, "dev": true, "requires": { "lodash._getnative": "^3.0.0" @@ -13206,62 +14178,52 @@ }, "lodash._createset": { "version": "4.0.3", - "resolved": false, - "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=", + "bundled": true, "dev": true }, "lodash._getnative": { "version": "3.9.1", - "resolved": false, - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "bundled": true, "dev": true }, "lodash._root": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "bundled": true, "dev": true }, "lodash.clonedeep": { "version": "4.5.0", - "resolved": false, - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", + "bundled": true, "dev": true }, "lodash.restparam": { "version": "3.6.1", - "resolved": false, - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "bundled": true, "dev": true }, "lodash.union": { "version": "4.6.0", - "resolved": false, - "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=", + "bundled": true, "dev": true }, "lodash.uniq": { "version": "4.5.0", - "resolved": false, - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "bundled": true, "dev": true }, "lodash.without": { "version": "4.4.0", - "resolved": false, - "integrity": "sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=", + "bundled": true, "dev": true }, "lowercase-keys": { "version": "1.0.1", - "resolved": false, - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "bundled": true, "dev": true }, "lru-cache": { "version": "5.1.1", - "resolved": false, - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "bundled": true, "dev": true, "requires": { "yallist": "^3.0.2" @@ -13269,8 +14231,7 @@ }, "make-dir": { "version": "1.3.0", - "resolved": false, - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "bundled": true, "dev": true, "requires": { "pify": "^3.0.0" @@ -13278,8 +14239,7 @@ }, "make-fetch-happen": { "version": "5.0.2", - "resolved": false, - "integrity": "sha512-07JHC0r1ykIoruKO8ifMXu+xEU8qOXDFETylktdug6vJDACnP+HKevOu3PXyNPzFyTSlz8vrBYlBO1JZRe8Cag==", + "bundled": true, "dev": true, "requires": { "agentkeepalive": "^3.4.1", @@ -13297,20 +14257,17 @@ }, "meant": { "version": "1.0.2", - "resolved": false, - "integrity": "sha512-KN+1uowN/NK+sT/Lzx7WSGIj2u+3xe5n2LbwObfjOhPZiA+cCfCm6idVl0RkEfjThkw5XJ96CyRcanq6GmKtUg==", + "bundled": true, "dev": true }, "mime-db": { "version": "1.35.0", - "resolved": false, - "integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg==", + "bundled": true, "dev": true }, "mime-types": { "version": "2.1.19", - "resolved": false, - "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", + "bundled": true, "dev": true, "requires": { "mime-db": "~1.35.0" @@ -13318,8 +14275,7 @@ }, "minimatch": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13327,14 +14283,12 @@ }, "minimist": { "version": "1.2.5", - "resolved": false, - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "bundled": true, "dev": true }, "minizlib": { "version": "1.3.3", - "resolved": false, - "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "bundled": true, "dev": true, "requires": { "minipass": "^2.9.0" @@ -13342,8 +14296,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -13354,8 +14307,7 @@ }, "mississippi": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "bundled": true, "dev": true, "requires": { "concat-stream": "^1.5.0", @@ -13372,8 +14324,7 @@ }, "mkdirp": { "version": "0.5.5", - "resolved": false, - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "bundled": true, "dev": true, "requires": { "minimist": "^1.2.5" @@ -13381,16 +14332,14 @@ "dependencies": { "minimist": { "version": "1.2.5", - "resolved": false, - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "bundled": true, "dev": true } } }, "move-concurrently": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "bundled": true, "dev": true, "requires": { "aproba": "^1.1.1", @@ -13403,28 +14352,24 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true } } }, "ms": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "bundled": true, "dev": true }, "mute-stream": { "version": "0.0.7", - "resolved": false, - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "bundled": true, "dev": true }, "node-fetch-npm": { "version": "2.0.2", - "resolved": false, - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", + "bundled": true, "dev": true, "requires": { "encoding": "^0.1.11", @@ -13434,8 +14379,7 @@ }, "node-gyp": { "version": "5.1.0", - "resolved": false, - "integrity": "sha512-OUTryc5bt/P8zVgNUmC6xdXiDJxLMAW8cF5tLQOT9E5sOQj+UeQxnnPy74K3CLCa/SOjjBlbuzDLR8ANwA+wmw==", + "bundled": true, "dev": true, "requires": { "env-paths": "^2.2.0", @@ -13453,8 +14397,7 @@ }, "nopt": { "version": "4.0.3", - "resolved": false, - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "bundled": true, "dev": true, "requires": { "abbrev": "1", @@ -13463,8 +14406,7 @@ }, "normalize-package-data": { "version": "2.5.0", - "resolved": false, - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "bundled": true, "dev": true, "requires": { "hosted-git-info": "^2.1.4", @@ -13475,8 +14417,7 @@ "dependencies": { "resolve": { "version": "1.10.0", - "resolved": false, - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "bundled": true, "dev": true, "requires": { "path-parse": "^1.0.6" @@ -13486,8 +14427,7 @@ }, "npm-audit-report": { "version": "1.3.3", - "resolved": false, - "integrity": "sha512-8nH/JjsFfAWMvn474HB9mpmMjrnKb1Hx/oTAdjv4PT9iZBvBxiZ+wtDUapHCJwLqYGQVPaAfs+vL5+5k9QndXw==", + "bundled": true, "dev": true, "requires": { "cli-table3": "^0.5.0", @@ -13496,8 +14436,7 @@ }, "npm-bundled": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "bundled": true, "dev": true, "requires": { "npm-normalize-package-bin": "^1.0.1" @@ -13505,14 +14444,12 @@ }, "npm-cache-filename": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=", + "bundled": true, "dev": true }, "npm-install-checks": { "version": "3.0.2", - "resolved": false, - "integrity": "sha512-E4kzkyZDIWoin6uT5howP8VDvkM+E8IQDcHAycaAxMbwkqhIg5eEYALnXOl3Hq9MrkdQB/2/g1xwBINXdKSRkg==", + "bundled": true, "dev": true, "requires": { "semver": "^2.3.0 || 3.x || 4 || 5" @@ -13520,8 +14457,7 @@ }, "npm-lifecycle": { "version": "3.1.5", - "resolved": false, - "integrity": "sha512-lDLVkjfZmvmfvpvBzA4vzee9cn+Me4orq0QF8glbswJVEbIcSNWib7qGOffolysc3teCqbbPZZkzbr3GQZTL1g==", + "bundled": true, "dev": true, "requires": { "byline": "^5.0.0", @@ -13536,20 +14472,17 @@ }, "npm-logical-tree": { "version": "1.2.1", - "resolved": false, - "integrity": "sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==", + "bundled": true, "dev": true }, "npm-normalize-package-bin": { "version": "1.0.1", - "resolved": false, - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "bundled": true, "dev": true }, "npm-package-arg": { "version": "6.1.1", - "resolved": false, - "integrity": "sha512-qBpssaL3IOZWi5vEKUKW0cO7kzLeT+EQO9W8RsLOZf76KF9E/K9+wH0C7t06HXPpaH8WH5xF1MExLuCwbTqRUg==", + "bundled": true, "dev": true, "requires": { "hosted-git-info": "^2.7.1", @@ -13560,8 +14493,7 @@ }, "npm-packlist": { "version": "1.4.8", - "resolved": false, - "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "bundled": true, "dev": true, "requires": { "ignore-walk": "^3.0.1", @@ -13571,8 +14503,7 @@ }, "npm-pick-manifest": { "version": "3.0.2", - "resolved": false, - "integrity": "sha512-wNprTNg+X5nf+tDi+hbjdHhM4bX+mKqv6XmPh7B5eG+QY9VARfQPfCEH013H5GqfNj6ee8Ij2fg8yk0mzps1Vw==", + "bundled": true, "dev": true, "requires": { "figgy-pudding": "^3.5.1", @@ -13582,8 +14513,7 @@ }, "npm-profile": { "version": "4.0.4", - "resolved": false, - "integrity": "sha512-Ta8xq8TLMpqssF0H60BXS1A90iMoM6GeKwsmravJ6wYjWwSzcYBTdyWa3DZCYqPutacBMEm7cxiOkiIeCUAHDQ==", + "bundled": true, "dev": true, "requires": { "aproba": "^1.1.2 || 2", @@ -13593,8 +14523,7 @@ }, "npm-registry-fetch": { "version": "4.0.7", - "resolved": false, - "integrity": "sha512-cny9v0+Mq6Tjz+e0erFAB+RYJ/AVGzkjnISiobqP8OWj9c9FLoZZu8/SPSKJWE17F1tk4018wfjV+ZbIbqC7fQ==", + "bundled": true, "dev": true, "requires": { "JSONStream": "^1.3.4", @@ -13608,25 +14537,27 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "resolved": false, - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "bundled": true, "dev": true } } }, "npm-run-path": { "version": "2.0.2", - "resolved": false, - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "bundled": true, "dev": true, "requires": { "path-key": "^2.0.0" } }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, "npmlog": { "version": "4.1.2", - "resolved": false, - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -13637,32 +14568,27 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "bundled": true, "dev": true }, "oauth-sign": { "version": "0.9.0", - "resolved": false, - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "bundled": true, "dev": true }, "object-assign": { "version": "4.1.1", - "resolved": false, - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "dev": true }, "object-keys": { "version": "1.0.12", - "resolved": false, - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", + "bundled": true, "dev": true }, "object.getownpropertydescriptors": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "bundled": true, "dev": true, "requires": { "define-properties": "^1.1.2", @@ -13671,8 +14597,7 @@ }, "once": { "version": "1.4.0", - "resolved": false, - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "dev": true, "requires": { "wrappy": "1" @@ -13680,26 +14605,22 @@ }, "opener": { "version": "1.5.1", - "resolved": false, - "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", + "bundled": true, "dev": true }, "os-homedir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "dev": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "dev": true }, "osenv": { "version": "0.1.5", - "resolved": false, - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "dev": true, "requires": { "os-homedir": "^1.0.0", @@ -13708,14 +14629,12 @@ }, "p-finally": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "bundled": true, "dev": true }, "package-json": { "version": "4.0.1", - "resolved": false, - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "bundled": true, "dev": true, "requires": { "got": "^6.7.1", @@ -13726,8 +14645,7 @@ }, "pacote": { "version": "9.5.12", - "resolved": false, - "integrity": "sha512-BUIj/4kKbwWg4RtnBncXPJd15piFSVNpTzY0rysSr3VnMowTYgkGKcaHrbReepAkjTr8lH2CVWRi58Spg2CicQ==", + "bundled": true, "dev": true, "requires": { "bluebird": "^3.5.3", @@ -13764,8 +14682,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -13776,8 +14693,7 @@ }, "parallel-transform": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "bundled": true, "dev": true, "requires": { "cyclist": "~0.2.2", @@ -13787,8 +14703,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -13802,8 +14717,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -13813,68 +14727,57 @@ }, "path-exists": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "bundled": true, "dev": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "dev": true }, "path-is-inside": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "bundled": true, "dev": true }, "path-key": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "bundled": true, "dev": true }, "path-parse": { "version": "1.0.6", - "resolved": false, - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "bundled": true, "dev": true }, "performance-now": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "bundled": true, "dev": true }, "pify": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "bundled": true, "dev": true }, "prepend-http": { "version": "1.0.4", - "resolved": false, - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "bundled": true, "dev": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "dev": true }, "promise-inflight": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "bundled": true, "dev": true }, "promise-retry": { "version": "1.1.1", - "resolved": false, - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", + "bundled": true, "dev": true, "requires": { "err-code": "^1.0.0", @@ -13883,16 +14786,14 @@ "dependencies": { "retry": { "version": "0.10.1", - "resolved": false, - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=", + "bundled": true, "dev": true } } }, "promzard": { "version": "0.3.0", - "resolved": false, - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "bundled": true, "dev": true, "requires": { "read": "1" @@ -13900,14 +14801,12 @@ }, "proto-list": { "version": "1.2.4", - "resolved": false, - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "bundled": true, "dev": true }, "protoduck": { "version": "5.0.1", - "resolved": false, - "integrity": "sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==", + "bundled": true, "dev": true, "requires": { "genfun": "^5.0.0" @@ -13915,26 +14814,22 @@ }, "prr": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "bundled": true, "dev": true }, "pseudomap": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "bundled": true, "dev": true }, "psl": { "version": "1.1.29", - "resolved": false, - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "bundled": true, "dev": true }, "pump": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "bundled": true, "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -13943,8 +14838,7 @@ }, "pumpify": { "version": "1.5.1", - "resolved": false, - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "bundled": true, "dev": true, "requires": { "duplexify": "^3.6.0", @@ -13954,8 +14848,7 @@ "dependencies": { "pump": { "version": "2.0.1", - "resolved": false, - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "bundled": true, "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -13966,26 +14859,22 @@ }, "punycode": { "version": "1.4.1", - "resolved": false, - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "bundled": true, "dev": true }, "qrcode-terminal": { "version": "0.12.0", - "resolved": false, - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "bundled": true, "dev": true }, "qs": { "version": "6.5.2", - "resolved": false, - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "bundled": true, "dev": true }, "query-string": { "version": "6.8.2", - "resolved": false, - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", + "bundled": true, "dev": true, "requires": { "decode-uri-component": "^0.2.0", @@ -13995,14 +14884,12 @@ }, "qw": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=", + "bundled": true, "dev": true }, "rc": { "version": "1.2.8", - "resolved": false, - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "bundled": true, "dev": true, "requires": { "deep-extend": "^0.6.0", @@ -14013,8 +14900,7 @@ }, "read": { "version": "1.0.7", - "resolved": false, - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "bundled": true, "dev": true, "requires": { "mute-stream": "~0.0.4" @@ -14022,8 +14908,7 @@ }, "read-cmd-shim": { "version": "1.0.5", - "resolved": false, - "integrity": "sha512-v5yCqQ/7okKoZZkBQUAfTsQ3sVJtXdNfbPnI5cceppoxEVLYA3k+VtV2omkeo8MS94JCy4fSiUwlRBAwCVRPUA==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2" @@ -14031,8 +14916,7 @@ }, "read-installed": { "version": "4.0.3", - "resolved": false, - "integrity": "sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=", + "bundled": true, "dev": true, "requires": { "debuglog": "^1.0.1", @@ -14046,8 +14930,7 @@ }, "read-package-json": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-dAiqGtVc/q5doFz6096CcnXhpYk0ZN8dEKVkGLU0CsASt8SrgF6SF7OTKAYubfvFhWaqofl+Y8HK19GR8jwW+A==", + "bundled": true, "dev": true, "requires": { "glob": "^7.1.1", @@ -14059,8 +14942,7 @@ }, "read-package-tree": { "version": "5.3.1", - "resolved": false, - "integrity": "sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw==", + "bundled": true, "dev": true, "requires": { "read-package-json": "^2.0.0", @@ -14070,8 +14952,7 @@ }, "readable-stream": { "version": "3.6.0", - "resolved": false, - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "bundled": true, "dev": true, "requires": { "inherits": "^2.0.3", @@ -14081,8 +14962,7 @@ }, "readdir-scoped-modules": { "version": "1.1.0", - "resolved": false, - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "bundled": true, "dev": true, "requires": { "debuglog": "^1.0.1", @@ -14093,8 +14973,7 @@ }, "registry-auth-token": { "version": "3.4.0", - "resolved": false, - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "bundled": true, "dev": true, "requires": { "rc": "^1.1.6", @@ -14103,8 +14982,7 @@ }, "registry-url": { "version": "3.1.0", - "resolved": false, - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "bundled": true, "dev": true, "requires": { "rc": "^1.0.1" @@ -14112,8 +14990,7 @@ }, "request": { "version": "2.88.0", - "resolved": false, - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "bundled": true, "dev": true, "requires": { "aws-sign2": "~0.7.0", @@ -14140,32 +15017,27 @@ }, "require-directory": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "bundled": true, "dev": true }, "require-main-filename": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "bundled": true, "dev": true }, "resolve-from": { "version": "4.0.0", - "resolved": false, - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "bundled": true, "dev": true }, "retry": { "version": "0.12.0", - "resolved": false, - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "bundled": true, "dev": true }, "rimraf": { "version": "2.7.1", - "resolved": false, - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "bundled": true, "dev": true, "requires": { "glob": "^7.1.3" @@ -14173,8 +15045,7 @@ }, "run-queue": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "bundled": true, "dev": true, "requires": { "aproba": "^1.1.1" @@ -14182,34 +15053,29 @@ "dependencies": { "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "dev": true } } }, "safe-buffer": { "version": "5.1.2", - "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "bundled": true, "dev": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "dev": true }, "semver": { "version": "5.7.1", - "resolved": false, - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bundled": true, "dev": true }, "semver-diff": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "bundled": true, "dev": true, "requires": { "semver": "^5.0.3" @@ -14217,14 +15083,12 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "dev": true }, "sha": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-DOYnM37cNsLNSGIG/zZWch5CKIRNoLdYUQTQlcgkRkoYIUwDYjqDyye16YcDZg/OPdcbUgTKMjc4SY6TB7ZAPw==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.2" @@ -14232,8 +15096,7 @@ }, "shebang-command": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "bundled": true, "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -14241,32 +15104,27 @@ }, "shebang-regex": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "bundled": true, "dev": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "dev": true }, "slide": { "version": "1.1.6", - "resolved": false, - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "bundled": true, "dev": true }, "smart-buffer": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==", + "bundled": true, "dev": true }, "socks": { "version": "2.3.3", - "resolved": false, - "integrity": "sha512-o5t52PCNtVdiOvzMry7wU4aOqYWL0PeCXRWBEiJow4/i/wr+wpsJQ9awEu1EonLIqsfGd5qSgDdxEOvCdmBEpA==", + "bundled": true, "dev": true, "requires": { "ip": "1.1.5", @@ -14275,8 +15133,7 @@ }, "socks-proxy-agent": { "version": "4.0.2", - "resolved": false, - "integrity": "sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==", + "bundled": true, "dev": true, "requires": { "agent-base": "~4.2.1", @@ -14285,8 +15142,7 @@ "dependencies": { "agent-base": { "version": "4.2.1", - "resolved": false, - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", + "bundled": true, "dev": true, "requires": { "es6-promisify": "^5.0.0" @@ -14296,14 +15152,12 @@ }, "sorted-object": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=", + "bundled": true, "dev": true }, "sorted-union-stream": { "version": "2.1.3", - "resolved": false, - "integrity": "sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=", + "bundled": true, "dev": true, "requires": { "from2": "^1.3.0", @@ -14312,8 +15166,7 @@ "dependencies": { "from2": { "version": "1.3.0", - "resolved": false, - "integrity": "sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=", + "bundled": true, "dev": true, "requires": { "inherits": "~2.0.1", @@ -14322,14 +15175,12 @@ }, "isarray": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "bundled": true, "dev": true }, "readable-stream": { "version": "1.1.14", - "resolved": false, - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14340,16 +15191,14 @@ }, "string_decoder": { "version": "0.10.31", - "resolved": false, - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "bundled": true, "dev": true } } }, "spdx-correct": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "bundled": true, "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -14358,14 +15207,12 @@ }, "spdx-exceptions": { "version": "2.1.0", - "resolved": false, - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "bundled": true, "dev": true }, "spdx-expression-parse": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "bundled": true, "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -14374,20 +15221,17 @@ }, "spdx-license-ids": { "version": "3.0.5", - "resolved": false, - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "bundled": true, "dev": true }, "split-on-first": { "version": "1.1.0", - "resolved": false, - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "bundled": true, "dev": true }, "sshpk": { "version": "1.14.2", - "resolved": false, - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", + "bundled": true, "dev": true, "requires": { "asn1": "~0.2.3", @@ -14403,8 +15247,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": false, - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "bundled": true, "dev": true, "requires": { "figgy-pudding": "^3.5.1" @@ -14412,8 +15255,7 @@ }, "stream-each": { "version": "1.2.2", - "resolved": false, - "integrity": "sha512-mc1dbFhGBxvTM3bIWmAAINbqiuAk9TATcfIQC8P+/+HJefgaiTlMn2dHvkX8qlI12KeYKSQ1Ua9RrIqrn1VPoA==", + "bundled": true, "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -14422,8 +15264,7 @@ }, "stream-iterate": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=", + "bundled": true, "dev": true, "requires": { "readable-stream": "^2.1.5", @@ -14432,8 +15273,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14447,8 +15287,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14458,20 +15297,17 @@ }, "stream-shift": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "bundled": true, "dev": true }, "strict-uri-encode": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", + "bundled": true, "dev": true }, "string-width": { "version": "2.1.1", - "resolved": false, - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "bundled": true, "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -14480,20 +15316,17 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "bundled": true, "dev": true }, "strip-ansi": { "version": "4.0.0", - "resolved": false, - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -14503,8 +15336,7 @@ }, "string_decoder": { "version": "1.3.0", - "resolved": false, - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.2.0" @@ -14512,22 +15344,19 @@ "dependencies": { "safe-buffer": { "version": "5.2.0", - "resolved": false, - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "bundled": true, "dev": true } } }, "stringify-package": { "version": "1.0.1", - "resolved": false, - "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "bundled": true, "dev": true }, "strip-ansi": { "version": "3.0.1", - "resolved": false, - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -14535,20 +15364,17 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "bundled": true, "dev": true }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "dev": true }, "supports-color": { "version": "5.4.0", - "resolved": false, - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "bundled": true, "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14556,8 +15382,7 @@ }, "tar": { "version": "4.4.13", - "resolved": false, - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "bundled": true, "dev": true, "requires": { "chownr": "^1.1.1", @@ -14571,8 +15396,7 @@ "dependencies": { "minipass": { "version": "2.9.0", - "resolved": false, - "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -14583,8 +15407,7 @@ }, "term-size": { "version": "1.2.0", - "resolved": false, - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "bundled": true, "dev": true, "requires": { "execa": "^0.7.0" @@ -14592,20 +15415,17 @@ }, "text-table": { "version": "0.2.0", - "resolved": false, - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "bundled": true, "dev": true }, "through": { "version": "2.3.8", - "resolved": false, - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "bundled": true, "dev": true }, "through2": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "bundled": true, "dev": true, "requires": { "readable-stream": "^2.1.5", @@ -14614,8 +15434,7 @@ "dependencies": { "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "dev": true, "requires": { "core-util-is": "~1.0.0", @@ -14629,8 +15448,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14640,20 +15458,17 @@ }, "timed-out": { "version": "4.0.1", - "resolved": false, - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "bundled": true, "dev": true }, "tiny-relative-date": { "version": "1.3.0", - "resolved": false, - "integrity": "sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==", + "bundled": true, "dev": true }, "tough-cookie": { "version": "2.4.3", - "resolved": false, - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "bundled": true, "dev": true, "requires": { "psl": "^1.1.24", @@ -14662,8 +15477,7 @@ }, "tunnel-agent": { "version": "0.6.0", - "resolved": false, - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "bundled": true, "dev": true, "requires": { "safe-buffer": "^5.0.1" @@ -14671,33 +15485,28 @@ }, "tweetnacl": { "version": "0.14.5", - "resolved": false, - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "bundled": true, "dev": true, "optional": true }, "typedarray": { "version": "0.0.6", - "resolved": false, - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "bundled": true, "dev": true }, "uid-number": { "version": "0.0.6", - "resolved": false, - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=", + "bundled": true, "dev": true }, "umask": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=", + "bundled": true, "dev": true }, "unique-filename": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "bundled": true, "dev": true, "requires": { "unique-slug": "^2.0.0" @@ -14705,8 +15514,7 @@ }, "unique-slug": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", + "bundled": true, "dev": true, "requires": { "imurmurhash": "^0.1.4" @@ -14714,8 +15522,7 @@ }, "unique-string": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "bundled": true, "dev": true, "requires": { "crypto-random-string": "^1.0.0" @@ -14723,20 +15530,17 @@ }, "unpipe": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "bundled": true, "dev": true }, "unzip-response": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "bundled": true, "dev": true }, "update-notifier": { "version": "2.5.0", - "resolved": false, - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "bundled": true, "dev": true, "requires": { "boxen": "^1.2.1", @@ -14753,8 +15557,7 @@ }, "url-parse-lax": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "bundled": true, "dev": true, "requires": { "prepend-http": "^1.0.1" @@ -14762,20 +15565,17 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "dev": true }, "util-extend": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=", + "bundled": true, "dev": true }, "util-promisify": { "version": "2.1.0", - "resolved": false, - "integrity": "sha1-PCI2R2xNMsX/PEcAKt18E7moKlM=", + "bundled": true, "dev": true, "requires": { "object.getownpropertydescriptors": "^2.0.3" @@ -14783,14 +15583,12 @@ }, "uuid": { "version": "3.3.3", - "resolved": false, - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "bundled": true, "dev": true }, "validate-npm-package-license": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "bundled": true, "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -14799,8 +15597,7 @@ }, "validate-npm-package-name": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", + "bundled": true, "dev": true, "requires": { "builtins": "^1.0.3" @@ -14808,8 +15605,7 @@ }, "verror": { "version": "1.10.0", - "resolved": false, - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "bundled": true, "dev": true, "requires": { "assert-plus": "^1.0.0", @@ -14819,8 +15615,7 @@ }, "wcwidth": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "bundled": true, "dev": true, "requires": { "defaults": "^1.0.3" @@ -14828,8 +15623,7 @@ }, "which": { "version": "1.3.1", - "resolved": false, - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "bundled": true, "dev": true, "requires": { "isexe": "^2.0.0" @@ -14837,14 +15631,12 @@ }, "which-module": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "bundled": true, "dev": true }, "wide-align": { "version": "1.1.2", - "resolved": false, - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "bundled": true, "dev": true, "requires": { "string-width": "^1.0.2" @@ -14852,8 +15644,7 @@ "dependencies": { "string-width": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "bundled": true, "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -14865,8 +15656,7 @@ }, "widest-line": { "version": "2.0.1", - "resolved": false, - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", + "bundled": true, "dev": true, "requires": { "string-width": "^2.1.1" @@ -14874,8 +15664,7 @@ }, "worker-farm": { "version": "1.7.0", - "resolved": false, - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "bundled": true, "dev": true, "requires": { "errno": "~0.1.7" @@ -14883,8 +15672,7 @@ }, "wrap-ansi": { "version": "5.1.0", - "resolved": false, - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "bundled": true, "dev": true, "requires": { "ansi-styles": "^3.2.0", @@ -14894,20 +15682,17 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "bundled": true, "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "bundled": true, "dev": true }, "string-width": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "bundled": true, "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -14917,8 +15702,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": false, - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -14928,14 +15712,12 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "bundled": true, "dev": true }, "write-file-atomic": { "version": "2.4.3", - "resolved": false, - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "bundled": true, "dev": true, "requires": { "graceful-fs": "^4.1.11", @@ -14945,32 +15727,27 @@ }, "xdg-basedir": { "version": "3.0.0", - "resolved": false, - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "bundled": true, "dev": true }, "xtend": { "version": "4.0.1", - "resolved": false, - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "bundled": true, "dev": true }, "y18n": { "version": "4.0.0", - "resolved": false, - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "bundled": true, "dev": true }, "yallist": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "bundled": true, "dev": true }, "yargs": { "version": "14.2.3", - "resolved": false, - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "bundled": true, "dev": true, "requires": { "cliui": "^5.0.0", @@ -14988,14 +15765,12 @@ "dependencies": { "ansi-regex": { "version": "4.1.0", - "resolved": false, - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "bundled": true, "dev": true }, "find-up": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "bundled": true, "dev": true, "requires": { "locate-path": "^3.0.0" @@ -15003,14 +15778,12 @@ }, "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "bundled": true, "dev": true }, "locate-path": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "bundled": true, "dev": true, "requires": { "p-locate": "^3.0.0", @@ -15019,8 +15792,7 @@ }, "p-limit": { "version": "2.3.0", - "resolved": false, - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "bundled": true, "dev": true, "requires": { "p-try": "^2.0.0" @@ -15028,8 +15800,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": false, - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "bundled": true, "dev": true, "requires": { "p-limit": "^2.0.0" @@ -15037,14 +15808,12 @@ }, "p-try": { "version": "2.2.0", - "resolved": false, - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "bundled": true, "dev": true }, "string-width": { "version": "3.1.0", - "resolved": false, - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "bundled": true, "dev": true, "requires": { "emoji-regex": "^7.0.1", @@ -15054,8 +15823,7 @@ }, "strip-ansi": { "version": "5.2.0", - "resolved": false, - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "bundled": true, "dev": true, "requires": { "ansi-regex": "^4.1.0" @@ -15065,8 +15833,7 @@ }, "yargs-parser": { "version": "15.0.1", - "resolved": false, - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", + "bundled": true, "dev": true, "requires": { "camelcase": "^5.0.0", @@ -15075,8 +15842,7 @@ "dependencies": { "camelcase": { "version": "5.3.1", - "resolved": false, - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "bundled": true, "dev": true } } @@ -15149,9 +15915,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "lru-cache": { "version": "5.1.1", @@ -15193,12 +15959,6 @@ "path-key": "^2.0.0" } }, - "npm-user-validate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-user-validate/-/npm-user-validate-1.0.1.tgz", - "integrity": "sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==", - "dev": true - }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -15273,6 +16033,12 @@ } } }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true + }, "object-inspect": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", @@ -15639,9 +16405,9 @@ }, "dependencies": { "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "lru-cache": { "version": "5.1.1", @@ -15898,131 +16664,1818 @@ "pinkie": "^2.0.0" } }, - "pkg-conf": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", - "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "dev": true, + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "popper.js": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", + "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==", + "dev": true + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "positioning": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/positioning/-/positioning-2.0.1.tgz", + "integrity": "sha512-DsAgM42kV/ObuwlRpAzDTjH9E8fGKkMDJHWFX+kfNXSxh7UCCQxEmdjv/Ws5Ft1XDnt3JT8fIDYeKNSE2TbttA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postcss": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.6.tgz", + "integrity": "sha512-wG1cc/JhRgdqB6WHEuyLTedf3KIRuD0hG6ldkFEZNCjRxiC+3i6kkWUUbiJQayP28iwG35cEmAbe98585BYV0A==", + "dev": true, + "requires": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + }, + "dependencies": { + "colorette": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.3.0.tgz", + "integrity": "sha512-ecORCqbSFP7Wm8Y6lyqMJjexBQqXSF7SSeaTyGGphogUjBlFP9m9o08wy86HL2uB7fMTxtOUzLMk7ogKcxMg1w==", + "dev": true + } + } + }, + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "dev": true, + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-3.0.3.tgz", + "integrity": "sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1", + "postcss": "^8.1.6" + } + }, + "postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "dev": true, + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", "dev": true, "requires": { - "find-up": "^2.0.0", - "load-json-file": "^4.0.0" + "postcss": "^7.0.5" }, "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "color-convert": "^1.9.0" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "p-try": "^1.0.0" + "color-name": "1.1.3" } }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "please-upgrade-node": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", - "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", - "dev": true, - "requires": { - "semver-compare": "^1.0.0" - } - }, - "pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "dev": true, - "requires": { - "ts-pnp": "^1.1.6" - } - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "dev": true - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", "dev": true, "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "has-flag": "^3.0.0" } } } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", "dev": true, "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" }, "dependencies": { "ansi-styles": { @@ -16077,6 +18530,17 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -16094,340 +18558,117 @@ } } }, - "postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "dev": true, - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "dev": true, - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", - "dev": true, - "requires": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } - } - }, - "postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "dev": true, - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - } - }, - "postcss-loader": { + "postcss-modules-values": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", "dev": true, "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "minimist": "^1.2.0" + "color-convert": "^1.9.0" } }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" + "color-name": "1.1.3" } - } - } - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "dev": true, - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true - } - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", "dev": true, "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" } - } - } - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "dev": true, - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "dev": true, - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - } - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "dev": true, - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "postcss-nested": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", + "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", "dev": true, "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" + "postcss-selector-parser": "^6.0.6" }, "dependencies": { "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", "dev": true, "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" } } } }, - "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "dev": true, - "requires": { - "postcss": "^7.0.5" - } - }, - "postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "dev": true, - "requires": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "dev": true, - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - } - }, - "postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "dev": true, - "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, "postcss-normalize-charset": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", @@ -16435,6 +18676,86 @@ "dev": true, "requires": { "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-normalize-display-values": { @@ -16448,11 +18769,89 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16468,11 +18867,89 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16488,88 +18965,478 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "postcss-normalize-string": { + "postcss-normalize-timing-functions": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", "dev": true, "requires": { - "has": "^1.0.0", + "cssnano-util-get-match": "^4.0.0", "postcss": "^7.0.0", "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", "dev": true, "requires": { - "cssnano-util-get-match": "^4.0.0", + "browserslist": "^4.0.0", "postcss": "^7.0.0", "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "postcss-normalize-unicode": { + "postcss-normalize-url": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", "dev": true, "requires": { - "browserslist": "^4.0.0", + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", "postcss": "^7.0.0", "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true - } - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "dev": true, - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16583,11 +19450,89 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16602,26 +19547,184 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, "postcss-reduce-transforms": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", @@ -16634,11 +19737,89 @@ "postcss-value-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16666,11 +19847,89 @@ "svgo": "^1.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-value-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -16683,6 +19942,86 @@ "alphanum-sort": "^1.0.0", "postcss": "^7.0.0", "uniqs": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-value-parser": { @@ -16721,9 +20060,15 @@ "dev": true }, "prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, "process": { @@ -17165,6 +20510,26 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "purgecss": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/purgecss/-/purgecss-4.0.3.tgz", + "integrity": "sha512-PYOIn5ibRIP34PBU9zohUcCI09c7drPJJtTDAc0Q6QlRz2/CHQ8ywGLdE7ZhxU2VTqB7p5wkvj5Qcm05Rz3Jmw==", + "dev": true, + "requires": { + "commander": "^6.0.0", + "glob": "^7.0.0", + "postcss": "^8.2.1", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + } + } + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -17220,6 +20585,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -17473,6 +20839,24 @@ "esprima": "~4.0.0" } }, + "reduce-css-calc": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz", + "integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + } + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -18070,9 +21454,9 @@ } }, "semantic-release": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.3.0.tgz", - "integrity": "sha512-enhDayMZRP4nWcWAMBFHHB7THRaIcRdUAZv3lxd65pXs2ttzay7IeCvRRrGayRWExtnY0ulwRz5Ycp88Dv/UeQ==", + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-17.4.2.tgz", + "integrity": "sha512-TPLWuoe2L2DmgnQEh+OLWW5V1T+ZAa1xWuHXsuPAWEko0BqSdLPl+5+BlQ+D5Bp27S5gDJ1//Y1tgbmvUhnOCw==", "dev": true, "requires": { "@semantic-release/commit-analyzer": "^8.0.0", @@ -18081,19 +21465,19 @@ "@semantic-release/npm": "^7.0.0", "@semantic-release/release-notes-generator": "^9.0.0", "aggregate-error": "^3.0.0", - "cosmiconfig": "^6.0.0", + "cosmiconfig": "^7.0.0", "debug": "^4.0.0", "env-ci": "^5.0.0", - "execa": "^4.0.0", + "execa": "^5.0.0", "figures": "^3.0.0", - "find-versions": "^3.0.0", - "get-stream": "^5.0.0", + "find-versions": "^4.0.0", + "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^2.0.0", - "hosted-git-info": "^3.0.0", + "hosted-git-info": "^4.0.0", "lodash": "^4.17.15", - "marked": "^1.0.0", - "marked-terminal": "^4.0.0", + "marked": "^2.0.0", + "marked-terminal": "^4.1.1", "micromatch": "^4.0.2", "p-each-series": "^2.1.0", "p-reduce": "^2.0.0", @@ -18102,31 +21486,31 @@ "semver": "^7.3.2", "semver-diff": "^3.1.1", "signale": "^1.2.1", - "yargs": "^15.0.1" + "yargs": "^16.2.0" }, "dependencies": { "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "wrap-ansi": "^7.0.0" } }, "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", + "integrity": "sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==", "dev": true, "requires": { "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", + "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", - "yaml": "^1.7.2" + "yaml": "^1.10.0" } }, "cross-spawn": { @@ -18141,45 +21525,56 @@ } }, "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", "dev": true, "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "semver-regex": "^3.1.2" } }, "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true + }, + "hosted-git-info": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", + "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==", "dev": true, "requires": { - "pump": "^3.0.0" + "lru-cache": "^6.0.0" } }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -18200,15 +21595,6 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -18218,19 +21604,10 @@ "path-key": "^3.0.0" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -18239,12 +21616,6 @@ "lines-and-columns": "^1.1.6" } }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -18258,14 +21629,20 @@ "dev": true }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } }, + "semver-regex": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz", + "integrity": "sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA==", + "dev": true + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -18291,9 +21668,9 @@ } }, "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { "ansi-styles": "^4.0.0", @@ -18301,34 +21678,32 @@ "strip-ansi": "^6.0.0" } }, + "y18n": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.7.tgz", + "integrity": "sha512-oOhslryvNcA1lB9WYr+M6TMyLkLg81Dgmyb48ZDU0lvR+5bmNDTMz7iobM1QXooaLhbbrcHrlNaABhI6Vo6StQ==", + "dev": true + }, "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" } }, "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true } } }, @@ -18666,9 +22041,9 @@ "optional": true }, "simple-get": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", - "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "optional": true, "requires": { "decompress-response": "^4.2.0", @@ -18833,170 +22208,55 @@ } }, "socket.io": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.4.1.tgz", - "integrity": "sha512-Si18v0mMXGAqLqCVpTxBa8MGqriHGQh8ccEOhmsmNS3thNCGBwO8WGrwMibANsWtQQ5NStdZwHqZR3naJVFc3w==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.4.1.tgz", + "integrity": "sha512-s04vrBswdQBUmuWJuuNTmXUVJhP0cVky8bBDhdkf8y0Ptsu7fKU2LuLbts9g+pdmAdyMMn8F/9Mf1/wbtUN0fg==", "dev": true, "requires": { - "debug": "~4.1.0", - "engine.io": "~3.5.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", - "socket.io-client": "2.4.0", - "socket.io-parser": "~3.4.0" + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" }, "dependencies": { - "cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "dev": true - }, - "engine.io": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.5.0.tgz", - "integrity": "sha512-21HlvPUKaitDGE4GXNtQ7PLP0Sz4aWLddMPw2VTyFz1FVZqu/kZsJUO8WNpKuE/OCL7nkfRaOui2ZCJloGznGA==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", - "ws": "~7.4.2" - } - }, - "engine.io-client": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz", - "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==", + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { - "component-emitter": "~1.3.0", - "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.2.0", - "has-cors": "1.1.0", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "ws": "~7.4.2", - "xmlhttprequest-ssl": "~1.5.4", - "yeast": "0.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } + "ms": "2.1.2" } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, - "socket.io-client": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", - "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", - "dev": true, - "requires": { - "backo2": "1.0.2", - "component-bind": "1.0.0", - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "engine.io-client": "~3.5.0", - "has-binary2": "~1.0.2", - "indexof": "0.0.1", - "parseqs": "0.0.6", - "parseuri": "0.0.6", - "socket.io-parser": "~3.3.0", - "to-array": "0.1.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "socket.io-parser": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", - "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", - "dev": true, - "requires": { - "component-emitter": "~1.3.0", - "debug": "~3.1.0", - "isarray": "2.0.1" - } - } - } - }, - "ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", - "dev": true } } }, "socket.io-adapter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", - "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.3.3.tgz", + "integrity": "sha512-Qd/iwn3VskrpNO60BeRyCyr8ZWw9CPZyitW4AQwmRZ8zCiyDiL+znRnWX6tDHXnWn1sJrM1+b6Mn6wEDJJ4aYQ==", "dev": true }, "socket.io-parser": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", - "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", "dev": true, "requires": { - "component-emitter": "1.2.1", - "debug": "~4.1.0", - "isarray": "2.0.1" + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" }, "dependencies": { - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", - "dev": true + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "dev": true, + "requires": { + "ms": "2.1.2" + } } } }, @@ -19101,6 +22361,12 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true + }, "source-map-loader": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-1.0.2.tgz", @@ -19349,9 +22615,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -19576,6 +22842,69 @@ "postcss-selector-parser": "^3.0.0" }, "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "postcss": { + "version": "7.0.36", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.36.tgz", + "integrity": "sha512-BebJSIUMwJHRH0HAQoxN4u1CN86glsrwsW0q7T+/m44eXOUAxSNdHRkNZPYz5vVUbg17hFgOQDE7fZk7li3pZw==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, "postcss-selector-parser": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", @@ -19586,6 +22915,21 @@ "indexes-of": "^1.0.1", "uniq": "^1.0.1" } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -19676,9 +23020,9 @@ } }, "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", + "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", "dev": true, "requires": { "has-flag": "^4.0.0", @@ -19769,6 +23113,334 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, + "tailwindcss": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-2.2.9.tgz", + "integrity": "sha512-P8zCKFkEthfUvqcnun8DqGGXw4QqyDw971NAM23e8QQ+m5HW1agp4upq50rFGwGNtphVYvr+0zvVLSXo5/I9Qg==", + "dev": true, + "requires": { + "arg": "^5.0.1", + "bytes": "^3.0.0", + "chalk": "^4.1.2", + "chokidar": "^3.5.2", + "color": "^4.0.1", + "cosmiconfig": "^7.0.1", + "detective": "^5.2.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.7", + "fs-extra": "^10.0.0", + "glob-parent": "^6.0.1", + "html-tags": "^3.1.0", + "is-glob": "^4.0.1", + "lodash": "^4.17.21", + "lodash.topath": "^4.5.2", + "modern-normalize": "^1.1.0", + "node-emoji": "^1.11.0", + "normalize-path": "^3.0.0", + "object-hash": "^2.2.0", + "postcss-js": "^3.0.3", + "postcss-load-config": "^3.1.0", + "postcss-nested": "5.0.6", + "postcss-selector-parser": "^6.0.6", + "postcss-value-parser": "^4.1.0", + "pretty-hrtime": "^1.0.3", + "purgecss": "^4.0.3", + "quick-lru": "^5.1.1", + "reduce-css-calc": "^2.1.8", + "resolve": "^1.20.0", + "tmp": "^0.2.1" + }, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", + "integrity": "sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", + "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/color/-/color-4.0.1.tgz", + "integrity": "sha512-rpZjOKN5O7naJxkH2Rx1sZzzBgaiWECc6BYXjeCE6kF0kcASJYbUq02u7JqIHwCb/j3NhV+QhRL2683aICeGZA==", + "dev": true, + "requires": { + "color-convert": "^2.0.1", + "color-string": "^1.6.0" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.1.tgz", + "integrity": "sha512-kEVjS71mQazDBHKcsq4E9u/vUzaLcw1A8EtUeydawvIWQCJM0qQ08G1H7/XTjFUulla6XQiDOG6MXSaG0HDKog==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "requires": { + "import-from": "^3.0.0" + } + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + } + } + }, + "node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "requires": { + "lodash": "^4.17.21" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "postcss-load-config": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.0.tgz", + "integrity": "sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==", + "dev": true, + "requires": { + "import-cwd": "^3.0.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } + }, + "postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -19776,17 +23448,17 @@ "dev": true }, "tar": { - "version": "4.4.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", - "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "version": "4.4.19", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.19.tgz", + "integrity": "sha512-a20gEsvHnWe0ygBY8JbxoM4w3SJdhc7ZAuxkLqh+nvNQN2IOt0B5lLgM490X5Hl8FF0dl0tOf2ewFYAlIFgzVA==", "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" + "chownr": "^1.1.4", + "fs-minipass": "^1.2.7", + "minipass": "^2.9.0", + "minizlib": "^1.3.3", + "mkdirp": "^0.5.5", + "safe-buffer": "^5.2.1", + "yallist": "^3.1.1" }, "dependencies": { "yallist": { @@ -19992,18 +23664,18 @@ "dev": true }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" } }, "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", @@ -20065,12 +23737,6 @@ "os-tmpdir": "~1.0.2" } }, - "to-array": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", - "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", - "dev": true - }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -20161,15 +23827,15 @@ "dev": true }, "trim-newlines": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", - "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, "trim-off-newlines": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", - "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.3.tgz", + "integrity": "sha512-kh6Tu6GbeSNMGfrrZh6Bb/4ZEHV1QlB4xNDBeog8Y9/QwFlKTRyWvY3Fs9tRDAMZliVUwieMgEdIeL/FtqjkJg==", "dev": true }, "ts-node": { @@ -20192,14 +23858,14 @@ "dev": true }, "tslib": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", - "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" }, "tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.0.tgz", + "integrity": "sha512-fXjYd/61vU6da04E505OZQGb2VCN2Mq3doeWcOIryuG+eqdmFUXTYVwdhnbEu2k46LNLgUYt9bI5icQze/j0bQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -20210,10 +23876,10 @@ "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", + "mkdirp": "^0.5.1", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.13.0", + "tslib": "^1.10.0", "tsutils": "^2.29.0" }, "dependencies": { @@ -20272,6 +23938,12 @@ "requires": { "has-flag": "^3.0.0" } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true } } }, @@ -20366,15 +24038,15 @@ "dev": true }, "ua-parser-js": { - "version": "0.7.21", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", - "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==", + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", + "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", "dev": true }, "uglify-js": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.2.tgz", - "integrity": "sha512-rWYleAvfJPjduYCt+ELvzybNah/zIkRteGXIBO8X0lteRZPGladF61hFi8tU7qKTsF7u6DUQCtT9k00VlFOgkg==", + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.3.tgz", + "integrity": "sha512-otIc7O9LyxpUcQoXzj2hL4LPWKklO6LJWoJUzNa8A17Xgi4fOeDC8FBDOLHnC/Slo1CQgsZMcM6as0M76BZaig==", "dev": true, "optional": true }, @@ -20594,9 +24266,9 @@ "dev": true }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { "querystringify": "^2.1.1", @@ -20849,6 +24521,7 @@ "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -21169,14 +24842,6 @@ "ajv-keywords": "^3.1.0" } }, - "serialize-javascript": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", - "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", - "requires": { - "randombytes": "^2.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -21418,6 +25083,7 @@ "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -21848,9 +25514,9 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", + "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", "dev": true, "requires": { "async-limiter": "~1.0.0" @@ -21870,21 +25536,15 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" }, - "xmlhttprequest-ssl": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", - "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" }, "yallist": { "version": "4.0.0", @@ -21965,12 +25625,6 @@ "decamelize": "^1.2.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index c286b54b7..6e6bb6a32 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "time-tracker", - "version": "1.31.16", + "version": "2.6.0", "scripts": { "preinstall": "npx npm-force-resolutions", "ng": "ng", "start": "ng serve", - "build": "ng build", - "test": "ng test", + "build": "ng build --prod", + "build-legacy": "ng build --configuration productionlegacy", + "test": "ng test --browsers ChromeHeadless", "test-headless": "ng test --browsers ChromeHeadless", "ci-test": "ng test --no-watch --no-progress --browsers ChromeHeadless", "lint": "ng lint", @@ -14,82 +15,97 @@ }, "private": true, "dependencies": { - "@angular/animations": "^10.2.2", - "@angular/common": "~10.2.2", - "@angular/compiler": "~10.2.2", - "@angular/core": "~10.2.2", - "@angular/forms": "~10.2.2", - "@angular/platform-browser": "~10.2.2", - "@angular/platform-browser-dynamic": "~10.2.2", - "@angular/router": "~10.2.2", - "@azure/app-configuration": "^1.1.0", - "@azure/identity": "^1.1.0", - "@ngrx/effects": "^10.0.1", - "@ngrx/store": "^10.0.1", - "@ngrx/store-devtools": "^10.0.1", - "@types/datatables.net-buttons": "^1.4.3", - "angular-datatables": "^9.0.2", - "bootstrap": "^4.4.1", - "datatables.net": "^1.10.21", - "datatables.net-buttons": "^1.6.2", - "datatables.net-buttons-dt": "^1.6.2", - "datatables.net-dt": "^1.10.21", - "datatables.net-responsive": "^2.2.6", - "datatables.net-responsive-dt": "^2.2.6", - "jquery": "^3.5.1", - "jszip": "^3.4.0", - "minimist": "^1.2.5", - "moment": "^2.25.3", - "msal": "^1.2.1", - "ngx-cookie-service": "^11.0.2", - "ngx-mask": "^9.1.2", - "ngx-material-timepicker": "^5.5.3", - "ngx-pagination": "^5.0.0", - "ngx-toastr": "^12.0.1", - "ngx-ui-switch": "^10.0.2", - "rxjs": "~6.6.3", - "tslib": "^1.10.0", - "zone.js": "~0.10.2" + "@angular/animations": "10.2.2", + "@angular/cdk": "11.2.3", + "@angular/common": "10.2.2", + "@angular/compiler": "10.2.2", + "@angular/core": "10.2.2", + "@angular/forms": "10.2.2", + "@angular/material": "11.2.3", + "@angular/material-moment-adapter": "11.2.9", + "@angular/platform-browser": "10.2.2", + "@angular/platform-browser-dynamic": "10.2.2", + "@angular/router": "10.2.2", + "@auth0/angular-jwt": "^5.0.2", + "@azure/app-configuration": "1.1.0", + "@azure/identity": "1.1.0", + "@ng-select/ng-select": "7.2.0", + "@ngneat/tailwind": "^5.2.5", + "@ngrx/effects": "10.0.1", + "@ngrx/store": "10.0.1", + "@ngrx/store-devtools": "10.0.1", + "@types/datatables.net-buttons": "1.4.3", + "angular-calendar": "0.28.26", + "angular-datatables": "9.0.2", + "angularx-social-login": "^3.5.7", + "bootstrap": "4.4.1", + "datatables.net": "1.10.22", + "datatables.net-buttons": "1.6.2", + "datatables.net-buttons-dt": "1.6.2", + "datatables.net-dt": "1.10.21", + "datatables.net-responsive": "2.2.6", + "datatables.net-responsive-dt": "2.2.6", + "date-fns": "2.22.1", + "dotenv": "^14.2.0", + "jquery": "3.5.1", + "jszip": "3.7.0", + "minimist": "1.2.5", + "moment": "2.25.3", + "msal": "1.2.1", + "ngrx-store-localstorage": "11.0.0", + "ngx-cookie-service": "11.0.2", + "ngx-mask": "12.0.0", + "ngx-material-timepicker": "5.5.3", + "ngx-pagination": "5.0.0", + "ngx-toastr": "12.0.1", + "ngx-ui-switch": "10.0.2", + "rxjs": "6.6.3", + "tslib": "1.10.0", + "zone.js": "0.10.3" }, "devDependencies": { - "@angular-devkit/build-angular": "^0.1002.0", - "@angular/cli": "~10.2.0", - "@angular/compiler-cli": "~10.2.2", - "@angular/language-service": "~10.2.2", - "@semantic-release/changelog": "^5.0.1", - "@semantic-release/commit-analyzer": "^8.0.1", - "@semantic-release/git": "^9.0.0", - "@semantic-release/npm": "^7.0.5", + "@angular-builders/custom-webpack": "10.0.1", + "@angular-devkit/build-angular": "0.1002.0", + "@angular/cli": "10.2.0", + "@angular/compiler-cli": "10.2.2", + "@angular/language-service": "10.2.2", + "@semantic-release/changelog": "5.0.1", + "@semantic-release/commit-analyzer": "8.0.1", + "@semantic-release/git": "9.0.0", + "@semantic-release/npm": "7.0.5", "@stryker-mutator/core": "^3.1.0", "@stryker-mutator/karma-runner": "^3.1.0", "@stryker-mutator/typescript": "^3.1.0", - "@types/datatables.net": "^1.10.19", - "@types/jasmine": "~3.5.0", - "@types/jasminewd2": "~2.0.3", - "@types/jquery": "^3.3.38", - "@types/node": "^12.11.1", - "angular-datatables": "^9.0.2", - "angular-ng-autocomplete": "^2.0.1", - "codelyzer": "^5.1.2", - "commit-message-validator": "^0.1.11", - "datatables.net": "^1.10.21", - "datatables.net-dt": "^1.10.21", - "husky": "^4.2.3", - "jasmine-core": "~3.5.0", - "jquery": "^3.5.1", - "karma": "^5.0.1", - "karma-chrome-launcher": "~3.1.0", - "karma-coverage-istanbul-reporter": "~2.1.0", - "karma-jasmine": "~2.0.1", - "karma-jasmine-html-reporter": "^1.4.2", + "@tailwindcss/forms": "0.3.3", + "@tailwindcss/typography": "0.4.1", + "@types/datatables.net": "1.10.19", + "@types/jasmine": "3.5.0", + "@types/jasminewd2": "2.0.3", + "@types/jquery": "3.3.38", + "@types/node": "12.11.1", + "angular-datatables": "9.0.2", + "angular-ng-autocomplete": "2.0.1", + "codelyzer": "5.2.2", + "commit-message-validator": "0.1.11", + "datatables.net": "1.10.22", + "datatables.net-dt": "1.10.21", + "husky": "4.2.3", + "jasmine-core": "3.5.0", + "karma": "6.3.16", + "karma-chrome-launcher": "3.1.0", + "karma-coverage-istanbul-reporter": "2.1.0", + "karma-jasmine": "2.0.1", + "karma-jasmine-html-reporter": "1.4.2", "karma-json-fixtures-preprocessor": "0.0.6", - "karma-spec-reporter": "^0.0.32", - "popper.js": "^1.16.0", - "prettier": "^2.0.2", - "protractor": "^7.0.0", - "semantic-release": "^17.3.0", - "ts-node": "~8.3.0", - "tslint": "~6.1.0", + "karma-spec-reporter": "0.0.32", + "popper.js": "1.16.0", + "postcss": "8.3.6", + "prettier": "2.0.2", + "protractor": "7.0.0", + "semantic-release": "17.4.2", + "tailwindcss": "2.2.9", + "ts-node": "8.3.0", + "tslint": "6.1.0", "typescript": "4.0.5" }, "husky": { @@ -100,8 +116,8 @@ }, "config": { "commit-message-validator": { - "pattern": "^(fix: TT-|feat: TT-|perf: TT-|build: TT-|ci: TT-|docs: TT-|refactor: TT-|style: TT-|test: TT-)[0-9].*", - "errorMessage": "Your commit message needs to start with fix: , feat:, or perf: followed by any commit message, e.g. fix: TT-43 any commit message" + "pattern": "^(fix: TTL-|feat: TTL-|perf: TTL-|build: TTL-|ci: TTL-|docs: TTL-|refactor: TTL-|style: TTL-|test: TTL-|code-smell: TTL-)[0-9].*", + "errorMessage": "\nYour commit message must comply with the following pattern:\n ^(fix: TTL-|feat: TTL-|perf: TTL-|build: TTL-|ci: TTL-|docs: TTL-|refactor: TTL-|style: TTL-|test: TTL-|code-smell: TTL-)[0-9].*\n followed by any commit message.\n\n Example:\n fix: TTL-43 any commit message\n" } }, "resolutions": { diff --git a/scripts/populate-keys.sh b/scripts/populate-keys.sh index f395689af..7dd584214 100644 --- a/scripts/populate-keys.sh +++ b/scripts/populate-keys.sh @@ -1,10 +1,12 @@ #!/bin/bash -> src/environments/keys.ts -echo 'export const AUTHORITY = "'$AUTHORITY'";' >> src/environments/keys.ts -echo 'export const CLIENT_ID = "'$CLIENT_ID'";' >> src/environments/keys.ts -echo 'export const SCOPES = ["'$SCOPES'"];' >> src/environments/keys.ts -echo 'export const STACK_EXCHANGE_ID = "'$STACK_EXCHANGE_ID'";' >> src/environments/keys.ts -echo 'export const STACK_EXCHANGE_ACCESS_TOKEN = "'$STACK_EXCHANGE_ACCESS_TOKEN'";' >> src/environments/keys.ts -echo 'export const AZURE_APP_CONFIGURATION_CONNECTION_STRING = "'$AZURE_APP_CONFIGURATION_CONNECTION_STRING'";' >> src/environments/keys.ts -cat src/environments/keys.ts +> .env +echo "API_URL='$API_URL'" >> .env +echo "AUTHORITY='$AUTHORITY'" >> .env +echo "CLIENT_ID='$CLIENT_ID'" >> .env +echo "CLIENT_URL='$CLIENT_URL'" >> .env +echo "SCOPES='$SCOPES'" >> .env +echo "STACK_EXCHANGE_ID='$STACK_EXCHANGE_ID'" >> .env +echo "STACK_EXCHANGE_ACCESS_TOKEN='$STACK_EXCHANGE_ACCESS_TOKEN'" >> .env +echo "AZURE_APP_CONFIGURATION_CONNECTION_STRING='$AZURE_APP_CONFIGURATION_CONNECTION_STRING'" >> .env +cat .env diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 9840d7461..916ba22e8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,4 +1,4 @@ -import { AdminGuard } from './guards/admin-guard/admin-guard'; +import { AdminGuard } from './guards/admin-guard/admin.guard'; import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; @@ -11,30 +11,46 @@ import { HomeComponent } from './modules/home/home.component'; import { LoginComponent } from './modules/login/login.component'; import { CustomerComponent } from './modules/customer-management/pages/customer.component'; import { UsersComponent } from './modules/users/pages/users.component'; -import { TechnologyReportComponent } from './modules/technology-report/pages/technology-report.component'; -import { TechnologiesReportGuard } from './guards/technologies-report-guard/technologies-report.guard'; +import { V2RedirectComponent } from './modules/v2-redirect/v2-redirect.component'; +import { EnvironmentType } from 'src/environments/enum'; +import { environment } from 'src/environments/environment'; -const routes: Routes = [ - { - path: '', - component: HomeComponent, - canActivate: [LoginGuard], - children: [ - { path: 'reports', canActivate: [AdminGuard], component: ReportsComponent }, - { path: 'time-clock', component: TimeClockComponent }, - { path: 'time-entries', component: TimeEntriesComponent }, - { path: 'activities-management', component: ActivitiesManagementComponent }, - { path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent }, - { path: 'users', canActivate: [AdminGuard], component: UsersComponent }, - { path: 'technology-report', canActivate: [AdminGuard, TechnologiesReportGuard], component: TechnologyReportComponent}, - { path: '', pathMatch: 'full', redirectTo: 'time-clock' }, - ], - }, - { path: 'login', component: LoginComponent }, -]; +const isNotLegacy: boolean = environment.production !== EnvironmentType.TT_PROD_LEGACY; +let routes: Routes; +if (isNotLegacy) { + routes = [ + { + path: '', + component: HomeComponent, + canActivate: [LoginGuard], + children: [ + { path: 'reports', canActivate: [AdminGuard], component: ReportsComponent }, + { path: 'time-clock', component: TimeClockComponent }, + { path: 'time-entries', component: TimeEntriesComponent }, + { path: 'activities-management', component: ActivitiesManagementComponent }, + { path: 'customers-management', canActivate: [AdminGuard], component: CustomerComponent }, + { path: 'users', canActivate: [AdminGuard], component: UsersComponent }, + { path: '', pathMatch: 'full', redirectTo: 'time-clock' }, + ], + }, + { path: 'login', component: LoginComponent } + ]; + +} else { + routes = [ + { + path: '', + children: [ + { path: '**', component: V2RedirectComponent }, + ], + }, + ]; +} @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) -export class AppRoutingModule {} +export class AppRoutingModule { + +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 0680b43f9..90c6b6463 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1 @@ - + \ No newline at end of file diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 505a1d050..db76b1685 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -3,16 +3,26 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ToastrModule } from 'ngx-toastr'; import { CommonModule, DatePipe } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; -import { NgModule, Component } from '@angular/core'; +import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { DataTablesModule } from 'angular-datatables'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { StoreDevtoolsModule } from '@ngrx/store-devtools'; - +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatInputModule } from '@angular/material/input'; +import { MatButtonModule } from '@angular/material/button'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatMomentDateModule } from '@angular/material-moment-adapter'; import { NgxPaginationModule } from 'ngx-pagination'; import { AutocompleteLibModule } from 'angular-ng-autocomplete'; +import { CalendarModule, DateAdapter } from 'angular-calendar'; +import { adapterFactory } from 'angular-calendar/date-adapters/date-fns'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -40,8 +50,9 @@ import { ActivityEffects } from './modules/activities-management/store/activity- import { ProjectEffects } from './modules/customer-management/components/projects/components/store/project.effects'; import { TechnologyEffects } from './modules/shared/store/technology.effects'; import { ProjectTypeEffects } from './modules/customer-management/components/projects-type/store/project-type.effects'; -import { reducers, metaReducers } from './reducers'; +import { reducers } from './reducers'; import { environment } from '../environments/environment'; +import { EnvironmentType } from '../environments/enum'; import { CustomerComponent } from './modules/customer-management/pages/customer.component'; // tslint:disable-next-line: max-line-length import { CustomerListComponent } from './modules/customer-management/components/customer-info/components/customer-list/customer-list.component'; @@ -56,10 +67,12 @@ import { ProjectTypeListComponent } from './modules/customer-management/componen // tslint:disable-next-line: max-line-length import { CreateProjectTypeComponent } from './modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component'; import { CustomerEffects } from './modules/customer-management/store/customer-management.effects'; -import { UserEffects } from './modules/users/store/user.effects'; +import { UserEffects as UsersEffects } from './modules/users/store/user.effects'; +import { UserEffects } from './modules/user/store/user.effects'; import { EntryEffects } from './modules/time-clock/store/entry.effects'; import { InjectTokenInterceptor } from './modules/shared/interceptors/inject.token.interceptor'; import { SubstractDatePipe } from './modules/shared/pipes/substract-date/substract-date.pipe'; +import { SubstractDatePipeDisplayAsFloat } from './modules/shared/pipes/substract-date-return-float/substract-date-return-float.pipe'; import { TechnologiesComponent } from './modules/shared/components/technologies/technologies.component'; import { TimeEntriesSummaryComponent } from './modules/time-clock/components/time-entries-summary/time-entries-summary.component'; import { TimeDetailsPipe } from './modules/time-clock/pipes/time-details.pipe'; @@ -73,10 +86,21 @@ import { LoadingBarComponent } from './modules/shared/components/loading-bar/loa import { UsersComponent } from './modules/users/pages/users.component'; import { UsersListComponent } from './modules/users/components/users-list/users-list.component'; import { UiSwitchModule } from 'ngx-ui-switch'; -import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker'; +import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; // tslint:disable-next-line: max-line-length -import { TechnologyReportTableComponent } from './modules/technology-report/components/technology-report-table/technology-report-table.component'; -import { TechnologyReportComponent } from './modules/technology-report/pages/technology-report.component'; +import { CalendarComponent } from './modules/time-entries/components/calendar/calendar.component'; +import { DropdownComponent } from './modules/shared/components/dropdown/dropdown.component'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { DarkModeComponent } from './modules/shared/components/dark-mode/dark-mode.component'; +import { SearchUserComponent } from './modules/shared/components/search-user/search-user.component'; +import { TimeRangeCustomComponent } from './modules/reports/components/time-range-custom/time-range-custom.component'; +import { TimeRangeHeaderComponent } from './modules/reports/components/time-range-custom/time-range-header/time-range-header.component'; +import { TimeRangeOptionsComponent } from './modules/reports/components/time-range-custom/time-range-options/time-range-options.component'; +import { V2RedirectComponent } from './modules/v2-redirect/v2-redirect.component'; +import { SpinnerOverlayComponent } from './modules/shared/components/spinner-overlay/spinner-overlay.component'; +import { SpinnerInterceptor } from './modules/shared/interceptors/spinner.interceptor'; +import { SearchProjectComponent } from './modules/shared/components/search-project/search-project.component'; +import { SearchActivityComponent } from './modules/shared/components/search-activity/search-activity.component'; const maskConfig: Partial = { validation: false, @@ -115,7 +139,11 @@ const maskConfig: Partial = { CreateProjectTypeComponent, EntryFieldsComponent, SubstractDatePipe, + SubstractDatePipeDisplayAsFloat, TechnologiesComponent, + SearchUserComponent, + SearchProjectComponent, + SearchActivityComponent, TimeEntriesSummaryComponent, TimeDetailsPipe, InputLabelComponent, @@ -127,11 +155,21 @@ const maskConfig: Partial = { LoadingBarComponent, UsersComponent, UsersListComponent, - TechnologyReportComponent, - TechnologyReportTableComponent, + CalendarComponent, + DropdownComponent, + DarkModeComponent, + TimeRangeCustomComponent, + TimeRangeHeaderComponent, + TimeRangeOptionsComponent, + V2RedirectComponent, + SpinnerOverlayComponent, ], imports: [ NgxMaskModule.forRoot(maskConfig), + MatCheckboxModule, + MatInputModule, + MatDatepickerModule, + MatMomentDateModule, CommonModule, BrowserModule, BrowserAnimationsModule, @@ -143,14 +181,17 @@ const maskConfig: Partial = { DataTablesModule, AutocompleteLibModule, NgxMaterialTimepickerModule, + MatProgressSpinnerModule, UiSwitchModule, + DragDropModule, + MatIconModule, + MatListModule, StoreModule.forRoot(reducers, { - metaReducers, }), - !environment.production + environment.production === EnvironmentType.TT_DEV ? StoreDevtoolsModule.instrument({ - maxAge: 15, // Retains last 15 states - }) + maxAge: 15, // Retains last 15 states + }) : [], EffectsModule.forRoot([ ProjectEffects, @@ -159,9 +200,15 @@ const maskConfig: Partial = { TechnologyEffects, ProjectTypeEffects, EntryEffects, + UsersEffects, UserEffects, ]), - ToastrModule.forRoot() + ToastrModule.forRoot(), + CalendarModule.forRoot({ + provide: DateAdapter, + useFactory: adapterFactory, + }), + NgSelectModule, ], providers: [ { @@ -169,9 +216,15 @@ const maskConfig: Partial = { useClass: InjectTokenInterceptor, multi: true, }, + { + provide: HTTP_INTERCEPTORS, + useClass: SpinnerInterceptor, + multi: true, + }, DatePipe, CookieService, + {provide: Window, useValue: window} ], bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/src/app/guards/admin-guard/admin-guard.ts b/src/app/guards/admin-guard/admin-guard.ts deleted file mode 100644 index 5c505e44f..000000000 --- a/src/app/guards/admin-guard/admin-guard.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router, CanActivate } from '@angular/router'; -import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service'; - -@Injectable({ - providedIn: 'root' -}) -export class AdminGuard implements CanActivate { - - constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) { } - - canActivate() { - if (this.azureAdB2CService.isAdmin()) { - return true; - } else { - this.router.navigate(['login']); - return false; - } -} -} diff --git a/src/app/guards/admin-guard/admin.guard.spec.ts b/src/app/guards/admin-guard/admin.guard.spec.ts index 53d513e60..35c404de0 100644 --- a/src/app/guards/admin-guard/admin.guard.spec.ts +++ b/src/app/guards/admin-guard/admin.guard.spec.ts @@ -1,57 +1,69 @@ import { inject, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; - -import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service'; -import { AdminGuard } from './admin-guard'; +import { of } from 'rxjs'; +import { UserInfoService } from 'src/app/modules/user/services/user-info.service'; +import { AdminGuard } from './admin.guard'; describe('AdminGuard', () => { - let adminGuard: AdminGuard; - let azureAdB2CService: AzureAdB2CService; - - const azureAdB2CServiceStub = { - isLogin() { - return true; - }, - isAdmin() { - return true; - } + let userInfoService: UserInfoService; + + const userInfoServiceStub = { + isAdmin: () => of(false), }; beforeEach(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], providers: [ - { providers: AzureAdB2CService, useValue: azureAdB2CServiceStub }, - ] + { provide: UserInfoService, useValue: userInfoServiceStub }, + ], }); adminGuard = TestBed.inject(AdminGuard); - azureAdB2CService = TestBed.inject(AzureAdB2CService); + userInfoService = TestBed.inject(UserInfoService); }); it('should be created', () => { expect(adminGuard).toBeTruthy(); }); - it('can activate the route when user is logged-in', () => { - spyOn(azureAdB2CService, 'isAdmin').and.returnValue(true); + const groupParams = [{ bool: false }, { bool: true }]; + groupParams.map((param) => { + it(`isAdminBasedInGroup return ${param.bool}`, () => { + spyOn(userInfoService, 'isAdmin').and.returnValue(of(param.bool)); - const canActivate = adminGuard.canActivate(); - - expect(azureAdB2CService.isAdmin).toHaveBeenCalled(); - expect(canActivate).toEqual(true); + adminGuard.isAdminBasedInGroup().subscribe((enabled) => { + expect(userInfoService.isAdmin).toHaveBeenCalled(); + expect(enabled).toBe(param.bool); + }); + }); }); - it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => { - spyOn(azureAdB2CService, 'isAdmin').and.returnValue(false); - spyOn(router, 'navigate').and.stub(); + const navigateParams = [ + { chosen: 'activate the route', isAdmin: true }, + { chosen: 'redirect to /login', isAdmin: false } + ]; + navigateParams.map((param) => { + it(`on isAdmin: ${param.isAdmin}, should ${param.chosen} `, inject( + [Router], + (router: Router) => { + const isAdmin$ = of(param.isAdmin); - const canActivate = adminGuard.canActivate(); + spyOn(adminGuard, 'isAdminBasedInGroup').and.returnValue(isAdmin$); + spyOn(router, 'navigate').and.stub(); - expect(azureAdB2CService.isAdmin).toHaveBeenCalled(); - expect(canActivate).toEqual(false); - expect(router.navigate).toHaveBeenCalledWith(['login']); - })); + const canActivate = adminGuard.canActivate(); + canActivate.subscribe((enabled) => { + if (!enabled) { + expect(router.navigate).toHaveBeenCalledWith(['login']); + } else { + expect(router.navigate).not.toHaveBeenCalled(); + expect(enabled).toBeTrue(); + } + }); + } + )); + }); }); diff --git a/src/app/guards/admin-guard/admin.guard.ts b/src/app/guards/admin-guard/admin.guard.ts new file mode 100644 index 000000000..ab9cb72df --- /dev/null +++ b/src/app/guards/admin-guard/admin.guard.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; +import { Router, CanActivate } from '@angular/router'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { UserInfoService } from 'src/app/modules/user/services/user-info.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AdminGuard implements CanActivate { + constructor( + private router: Router, + private userInfoService: UserInfoService, + ) { } + + canActivate(): Observable { + return this.isAdminBasedInGroup().pipe( + map((isAdmin: boolean) => { + if (!isAdmin) { this.router.navigate(['login']); } + return isAdmin; + }) + ); + } + + isAdminBasedInGroup(): Observable { + return this.userInfoService.isAdmin(); + } +} diff --git a/src/app/guards/login-guard/login.guard.spec.ts b/src/app/guards/login-guard/login.guard.spec.ts index 37d156541..b20c4a364 100644 --- a/src/app/guards/login-guard/login.guard.spec.ts +++ b/src/app/guards/login-guard/login.guard.spec.ts @@ -4,6 +4,10 @@ import { Router } from '@angular/router'; import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service'; import { LoginGuard } from './login.guard'; +import { LoginService } from '../../modules/login/services/login.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { SocialAuthService } from 'angularx-social-login'; +import { of } from 'rxjs'; describe('LoginGuard', () => { @@ -12,37 +16,72 @@ describe('LoginGuard', () => { let azureAdB2CService: AzureAdB2CService; const azureAdB2CServiceStub = { isLogin() { - return true; + return of(true); } }; + let loginService: LoginService; + const loginServiceStub = { + isLogin() { + return of(true); + } + }; + const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['']); beforeEach(() => { TestBed.configureTestingModule({ - imports: [ RouterTestingModule ], + imports: [ RouterTestingModule, HttpClientTestingModule ], providers: [ { providers: AzureAdB2CService, useValue: azureAdB2CServiceStub}, + { providers: LoginService, useValue: loginServiceStub}, + { provide: SocialAuthService, useValue: socialAuthServiceStub } ] }); loginGuard = TestBed.inject(LoginGuard); azureAdB2CService = TestBed.inject(AzureAdB2CService); + loginService = TestBed.inject(LoginService); }); it('should be created', () => { expect(loginGuard).toBeTruthy(); }); - it('can activate the route when user is logged-in', () => { + it('can activate the route when user is logged-in on Production', () => { + loginGuard.isProduction = true; spyOn(azureAdB2CService, 'isLogin').and.returnValue(true); - const canActivate = loginGuard.canActivate(); + loginGuard.canActivate().subscribe(canActivate => { + expect(canActivate).toEqual(true); + }); expect(azureAdB2CService.isLogin).toHaveBeenCalled(); - expect(canActivate).toEqual(true); }); - it('can not active the route and is redirected to login if user is not logged-in', inject([Router], (router: Router) => { + + it('can activate the route when user is logged-in Locally', () => { + loginGuard.isProduction = false; + spyOn(loginService, 'isLogin').and.returnValue(of(true)); + loginGuard.canActivate().subscribe(isLogin => { + expect(isLogin).toEqual(true); + }); + expect(loginService.isLogin).toHaveBeenCalled(); + }); + + it('can not active the route and is redirected to login if user is not logged-in on Production', inject([Router], (router: Router) => { + loginGuard.isProduction = true; spyOn(azureAdB2CService, 'isLogin').and.returnValue(false); spyOn(router, 'navigate').and.stub(); - const canActivate = loginGuard.canActivate(); + loginGuard.canActivate().subscribe(canActivate => { + expect(canActivate).toEqual(false); + }); expect(azureAdB2CService.isLogin).toHaveBeenCalled(); - expect(canActivate).toEqual(false); + expect(router.navigate).toHaveBeenCalledWith(['login']); + })); + + it('can not active the route and is redirected to login if user is not logged-in Locally', inject([Router], (router: Router) => { + loginGuard.isProduction = false; + spyOn(loginService, 'isLogin').and.returnValue(of(false)); + spyOn(router, 'navigate').and.stub(); + loginGuard.canActivate().subscribe(isLogin => { + expect(isLogin).toEqual(false); + }); + expect(loginService.isLogin).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(['login']); })); diff --git a/src/app/guards/login-guard/login.guard.ts b/src/app/guards/login-guard/login.guard.ts index 162544da3..d8d6a0f6d 100644 --- a/src/app/guards/login-guard/login.guard.ts +++ b/src/app/guards/login-guard/login.guard.ts @@ -1,21 +1,44 @@ import { Injectable } from '@angular/core'; import { Router, CanActivate } from '@angular/router'; import { AzureAdB2CService } from '../../modules/login/services/azure.ad.b2c.service'; +import { LoginService } from '../../modules/login/services/login.service'; +import { environment } from 'src/environments/environment'; +import { map } from 'rxjs/operators'; +import { EnvironmentType } from 'src/environments/enum'; +import { of } from 'rxjs'; + @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class LoginGuard implements CanActivate { - - constructor(private azureAdB2CService: AzureAdB2CService, private router: Router) { } + isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; + constructor( + private azureAdB2CService: AzureAdB2CService, + private router: Router, + private loginService: LoginService + ) {} canActivate() { - if (this.azureAdB2CService.isLogin()) { + if (this.isProduction) { + if (this.azureAdB2CService.isLogin()) { this.azureAdB2CService.setCookies(); - return true; - } else { + return of(true); + } else { this.router.navigate(['login']); - return false; + return of(false); + } + } else { + return this.loginService.isLogin().pipe( + map(isLogin => { + if (!isLogin) { + this.router.navigate(['login']); + return false; + } + this.loginService.setCookies(); + return true; + }) + ); } } } diff --git a/src/app/guards/technologies-report-guard/technologies-report.guard.spec.ts b/src/app/guards/technologies-report-guard/technologies-report.guard.spec.ts deleted file mode 100644 index 06a2ced97..000000000 --- a/src/app/guards/technologies-report-guard/technologies-report.guard.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { inject, TestBed } from '@angular/core/testing'; -import { Router } from '@angular/router'; -import { RouterTestingModule } from '@angular/router/testing'; -import { Observable, of } from 'rxjs'; -import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service'; -import { TechnologiesReportGuard } from './technologies-report.guard'; - -describe('TechnologiesReportGuard', () => { - - let technologiesReportGuard: TechnologiesReportGuard; - let featureManagerService: FeatureManagerService; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ RouterTestingModule ] - }); - technologiesReportGuard = TestBed.inject(TechnologiesReportGuard); - featureManagerService = TestBed.inject(FeatureManagerService); - }); - - it('should be created', () => { - expect(technologiesReportGuard).toBeTruthy(); - }); - - it('can activate the route when feature is enabled for user', () => { - spyOn(featureManagerService, 'isToggleEnabledForUser').and.returnValue(of(true)); - - const canActivate: Observable = technologiesReportGuard.canActivate(); - - expect(featureManagerService.isToggleEnabledForUser).toHaveBeenCalled(); - canActivate.subscribe(value => expect(value).toEqual(true)); - }); - - it('can not active the route and is redirected to home if feature is not enabled for user', inject([Router], (router: Router) => { - spyOn(featureManagerService, 'isToggleEnabledForUser').and.returnValue(of(false)); - spyOn(router, 'navigate').and.stub(); - - const canActivate: Observable = technologiesReportGuard.canActivate(); - - expect(featureManagerService.isToggleEnabledForUser).toHaveBeenCalled(); - canActivate.subscribe(value => expect(value).toEqual(false)); - expect(router.navigate).toHaveBeenCalledWith(['']); - })); - -}); diff --git a/src/app/guards/technologies-report-guard/technologies-report.guard.ts b/src/app/guards/technologies-report-guard/technologies-report.guard.ts deleted file mode 100644 index 101c986e0..000000000 --- a/src/app/guards/technologies-report-guard/technologies-report.guard.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@angular/core'; -import { Router, CanActivate } from '@angular/router'; -import { map } from 'rxjs/operators'; -import { FeatureManagerService } from 'src/app/modules/shared/feature-toggles/feature-toggle-manager.service'; - -@Injectable({ - providedIn: 'root' -}) -export class TechnologiesReportGuard implements CanActivate { - - constructor( - private featureManagerService: FeatureManagerService, - private router: Router - ) { } - - canActivate() { - return this.featureManagerService - .isToggleEnabledForUser('ui-list-technologies') - .pipe(map((enabled) => { - if (enabled === true) { - return true; - } else { - this.router.navigate(['']); - return false; - } - })); - } -} diff --git a/src/app/modules/activities-management/components/activity-list/activity-list.component.html b/src/app/modules/activities-management/components/activity-list/activity-list.component.html index d366fd3d5..7ed60a39e 100644 --- a/src/app/modules/activities-management/components/activity-list/activity-list.component.html +++ b/src/app/modules/activities-management/components/activity-list/activity-list.component.html @@ -3,33 +3,33 @@ Activity ID - Activity - Options + Activity + Options + Status - + {{ activity.id }} - {{ activity.name }} - - - +
+ + + + + + + + + {{ activity.btnName }} + + @@ -42,7 +42,7 @@ tabindex="-1" role="dialog" aria-hidden="true" - [title]="'Delete Activity'" + [title]="'Disable Activity'" [body]="message" (closeModalEvent)="deleteActivity()" > diff --git a/src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts b/src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts index 0ebe48512..61594e1f8 100644 --- a/src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts +++ b/src/app/modules/activities-management/components/activity-list/activity-list.component.spec.ts @@ -3,7 +3,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { allActivities } from './../../store/activity-management.selectors'; import { ActivityState } from './../../store/activity-management.reducers'; -import { DeleteActivity, SetActivityToEdit } from './../../store/activity-management.actions'; +import { ArchiveActivity, SetActivityToEdit, UnarchiveActivity } from './../../store/activity-management.actions'; import { ActivityListComponent } from './activity-list.component'; describe('ActivityListComponent', () => { @@ -13,11 +13,31 @@ describe('ActivityListComponent', () => { let mockActivitiesSelector; const state = { - data: [{ id: 'id', name: 'name', description: 'description' }], + data: [{ id: '1', name: 'name', description: 'description', status: 'inactive' }], isLoading: false, message: '', activityIdToEdit: '', }; + const operationBtnProps = [ + { + key: 'active', + _status: false, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-success' + }, + { + key: 'inactive', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Inactive', + iconColor: 'text-danger' + }, + ]; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -48,12 +68,27 @@ describe('ActivityListComponent', () => { expect(store.dispatch).toHaveBeenCalled(); }); + it('onInit, activities field is populated with data from store', () => { + component.ngOnInit(); + + const expectedData = state.data.map(item => { + const props = operationBtnProps.find(prop => prop.key === item.status); + return { ...item, ...props }; + }); + + expect(component.activities).toEqual(expectedData); + }); + + afterEach(() => { + fixture.destroy(); + }); + it('deleteActivity, dispatchs DeleteActivity action', () => { spyOn(store, 'dispatch'); - component.idToDelete = 'id'; + component.idToModify = 'id'; component.deleteActivity(); - expect(store.dispatch).toHaveBeenCalledWith(new DeleteActivity('id')); + expect(store.dispatch).toHaveBeenCalledWith(new ArchiveActivity('id')); }); it('updateActivity, dispatchs SetActivityToEdit action', () => { @@ -64,13 +99,70 @@ describe('ActivityListComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new SetActivityToEdit('id')); }); - it('onInit, activities field is populated with data from store', () => { - component.ngOnInit(); + it('unarchiveActivity, dispatchs UnarchiveActivity action', () => { + spyOn(store, 'dispatch'); + component.idToModify = 'id'; + component.unarchiveActivity(); - expect(component.activities).toBe(state.data); + expect(store.dispatch).toHaveBeenCalledWith(new UnarchiveActivity('id')); }); - afterEach(() => { - fixture.destroy(); + it('openModal should set on true and display \"Are you sure you want to inactive activity\"', () => { + const message = 'Are you sure you want to disable activity name?'; + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'active', + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }; + + component.openModal(itemData); + expect(component.showModal).toBeTrue(); + expect(component.message).toBe(message); + }); + + it('changeOperation should call unarchiveActivity() on item._status = true', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'inactive', + key: 'inactive', + _status: true, + btnColor: 'btn-primary', + btnIcon: 'fa-arrow-circle-up', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-danger' + }; + + spyOn(component, 'unarchiveActivity'); + component.changeOperation(itemData); + expect(component.unarchiveActivity).toHaveBeenCalled(); + }); + + it('changeOperation should call openModal() on item._status = false', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'active', + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnIconTwo: 'fa-caret-check', + btnName: 'Archive', + iconColor: 'text-success' + }; + + spyOn(component, 'openModal'); + component.changeOperation(itemData); + expect(component.openModal).toHaveBeenCalled(); }); }); diff --git a/src/app/modules/activities-management/components/activity-list/activity-list.component.ts b/src/app/modules/activities-management/components/activity-list/activity-list.component.ts index 18c1e1468..3552a012a 100644 --- a/src/app/modules/activities-management/components/activity-list/activity-list.component.ts +++ b/src/app/modules/activities-management/components/activity-list/activity-list.component.ts @@ -1,51 +1,91 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { delay } from 'rxjs/operators'; +import { delay, map } from 'rxjs/operators'; import { getIsLoading } from 'src/app/modules/activities-management/store/activity-management.selectors'; -import { Activity } from '../../../shared/models'; +import { Activity, ActivityFront } from '../../../shared/models'; import { allActivities } from '../../store'; -import { DeleteActivity, LoadActivities, SetActivityToEdit } from './../../store/activity-management.actions'; +import { ArchiveActivity, LoadActivities, SetActivityToEdit, UnarchiveActivity } from './../../store/activity-management.actions'; import { ActivityState } from './../../store/activity-management.reducers'; - @Component({ selector: 'app-activity-list', templateUrl: './activity-list.component.html', styleUrls: ['./activity-list.component.scss'], }) export class ActivityListComponent implements OnInit { - activities: Activity[] = []; + @Output() changeValueShowActivityForm = new EventEmitter(); + showActivityForm: boolean; + @Input() showOptionInDevelopment: boolean; + + constructor(private store: Store) { + this.isLoading$ = store.pipe(delay(0), select(getIsLoading)); + } + activities: ActivityFront[] = []; showModal = false; activityToDelete: Activity; message: string; - idToDelete: string; + idToModify: string; isLoading$: Observable; - constructor(private store: Store) { - this.isLoading$ = store.pipe(delay(0), select(getIsLoading)); - } ngOnInit() { + const operationBtnProps = [{ + key: 'active', + _status: false, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-success' + }, { + key: 'inactive', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Inactive', + iconColor: 'text-danger' + }]; + this.store.dispatch(new LoadActivities()); - const activities$ = this.store.pipe(select(allActivities)); + const activities$ = this.store.pipe( + select(allActivities), + map((activity: Activity[]) => { + return activity.map(item => { + const addProps = operationBtnProps.find(prop => (prop.key === item.status)); + return { ...item, ...addProps }; + }); + }), + ); activities$.subscribe((response) => { this.activities = response; }); } - deleteActivity() { - this.store.dispatch(new DeleteActivity(this.idToDelete)); - this.showModal = true; + deleteActivity(): void { + this.store.dispatch(new ArchiveActivity(this.idToModify)); + this.showModal = false; } - updateActivity(activityId: string) { + updateActivity(activityId: string): void { this.store.dispatch(new SetActivityToEdit(activityId)); + this.showActivityForm = true; + this.changeValueShowActivityForm.emit(this.showActivityForm); } - openModal(item: Activity) { - this.idToDelete = item.id; - this.message = `Are you sure you want to delete ${item.name}?`; + unarchiveActivity(): void { + this.store.dispatch(new UnarchiveActivity(this.idToModify)); + this.showModal = false; + } + + openModal(item: Activity): void { + this.message = `Are you sure you want to disable activity ${item.name}?`; this.showModal = true; } + + changeOperation(item: ActivityFront): void { + this.idToModify = item.id; + !item._status ? this.openModal(item) : this.unarchiveActivity(); + } } diff --git a/src/app/modules/activities-management/components/create-activity/create-activity.component.html b/src/app/modules/activities-management/components/create-activity/create-activity.component.html index daec24118..a714502bd 100644 --- a/src/app/modules/activities-management/components/create-activity/create-activity.component.html +++ b/src/app/modules/activities-management/components/create-activity/create-activity.component.html @@ -1,8 +1,10 @@ -

Activities

-
- -
+
+
+ +
+
+
@@ -21,7 +23,7 @@

Activities

-
diff --git a/src/app/modules/activities-management/components/create-activity/create-activity.component.spec.ts b/src/app/modules/activities-management/components/create-activity/create-activity.component.spec.ts index 7629e0c67..856933e15 100644 --- a/src/app/modules/activities-management/components/create-activity/create-activity.component.spec.ts +++ b/src/app/modules/activities-management/components/create-activity/create-activity.component.spec.ts @@ -10,6 +10,7 @@ import { activityIdToEdit, allActivities, ResetActivityToEdit, + SetActivityToEdit, } from '../../store'; import { getActivityById } from '../../store/activity-management.selectors'; import { Activity } from 'src/app/modules/shared/models'; @@ -151,4 +152,12 @@ describe('CreateActivityComponent', () => { expect(store.dispatch).toHaveBeenCalledTimes(1); expect(store.dispatch).toHaveBeenCalledWith(new ResetActivityToEdit()); }); + + it('should dispatch an action on activateActivityForm', () => { + spyOn(store, 'dispatch'); + + component.activateActivityForm(); + + expect(store.dispatch).toHaveBeenCalledWith(new SetActivityToEdit(null)); + }); }); diff --git a/src/app/modules/activities-management/components/create-activity/create-activity.component.ts b/src/app/modules/activities-management/components/create-activity/create-activity.component.ts index 7460cfa5f..2383e8352 100644 --- a/src/app/modules/activities-management/components/create-activity/create-activity.component.ts +++ b/src/app/modules/activities-management/components/create-activity/create-activity.component.ts @@ -1,10 +1,9 @@ import { FormBuilder, Validators, FormGroup } from '@angular/forms'; -import { Component, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Store, select } from '@ngrx/store'; - import { Activity } from '../../../shared/models'; import { ActivityState } from './../../store/activity-management.reducers'; -import { CreateActivity, UpdateActivity, getActivityById, ResetActivityToEdit } from '../../store'; +import { CreateActivity, UpdateActivity, getActivityById, ResetActivityToEdit, SetActivityToEdit } from '../../store'; @Component({ selector: 'app-create-activity', @@ -12,9 +11,10 @@ import { CreateActivity, UpdateActivity, getActivityById, ResetActivityToEdit } styleUrls: ['./create-activity.component.scss'], }) export class CreateActivityComponent implements OnInit { + @Input() showActivityForm: boolean; + @Output() changeValueShowActivityForm = new EventEmitter(); activityForm: FormGroup; activityToEdit: Activity; - constructor(private formBuilder: FormBuilder, private store: Store) { this.activityForm = this.formBuilder.group({ name: ['', Validators.required], @@ -60,9 +60,20 @@ export class CreateActivityComponent implements OnInit { this.store.dispatch(new CreateActivity(activityData)); this.activityForm.get('description').setValue(''); } + this.showActivityForm = false; + this.changeValueShowActivityForm.emit(this.showActivityForm); } cancelButton() { + this.activityForm.reset(); this.store.dispatch(new ResetActivityToEdit()); + this.showActivityForm = false; + this.changeValueShowActivityForm.emit(this.showActivityForm); + } + + activateActivityForm() { + this.store.dispatch(new SetActivityToEdit(null)); + this.showActivityForm = true; + this.activityForm.reset(); } } diff --git a/src/app/modules/activities-management/pages/activities-management.component.html b/src/app/modules/activities-management/pages/activities-management.component.html index 92f75b9bf..cdaaf85fb 100644 --- a/src/app/modules/activities-management/pages/activities-management.component.html +++ b/src/app/modules/activities-management/pages/activities-management.component.html @@ -1,4 +1,14 @@ -
- - +
+
+ + +
+ +
diff --git a/src/app/modules/activities-management/pages/activities-management.component.spec.ts b/src/app/modules/activities-management/pages/activities-management.component.spec.ts index 92f8a918a..bccc22ad4 100644 --- a/src/app/modules/activities-management/pages/activities-management.component.spec.ts +++ b/src/app/modules/activities-management/pages/activities-management.component.spec.ts @@ -1,5 +1,15 @@ import { waitForAsync, TestBed, ComponentFixture } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { StoreModule } from '@ngrx/store'; +import { ReactiveFormsModule } from '@angular/forms'; +import { provideMockStore } from '@ngrx/store/testing'; + import { ActivitiesManagementComponent } from './activities-management.component'; +import { ActivityListComponent } from '../components/activity-list/activity-list.component'; +import { CreateActivityComponent } from '../components/create-activity/create-activity.component'; + + +const state = {}; describe('ActivitiesManagementComponent', () => { let component: ActivitiesManagementComponent; @@ -7,8 +17,13 @@ describe('ActivitiesManagementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [], - declarations: [ActivitiesManagementComponent] + imports: [ StoreModule.forRoot({}), ReactiveFormsModule ], + providers: [ provideMockStore({ initialState: state }) ], + declarations: [ + ActivitiesManagementComponent, + CreateActivityComponent, + ActivityListComponent, + ] }).compileComponents(); })); @@ -21,4 +36,13 @@ describe('ActivitiesManagementComponent', () => { it('should create the component', () => { expect(component).toBeTruthy(); }); + + it('should check if is in development environment', () => { + expect(component.showOptionInDevelopment).toBe(true); + }); + + it('should check if add new entry button is rendered', () => { + const addItemDebugElement = fixture.debugElement.query(By.css('div.col-12.px-0')).childNodes.length; + expect(addItemDebugElement).toBe(3); + }); }); diff --git a/src/app/modules/activities-management/pages/activities-management.component.ts b/src/app/modules/activities-management/pages/activities-management.component.ts index 94c1de433..7b4a1c492 100644 --- a/src/app/modules/activities-management/pages/activities-management.component.ts +++ b/src/app/modules/activities-management/pages/activities-management.component.ts @@ -1,10 +1,17 @@ -import { Component } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; +import { environment } from 'src/environments/environment'; +import { EnvironmentType } from 'src/environments/enum'; @Component({ selector: 'app-activities-management', templateUrl: './activities-management.component.html', styleUrls: ['./activities-management.component.scss'], }) -export class ActivitiesManagementComponent { - constructor() {} +export class ActivitiesManagementComponent implements OnInit { + @Input() showActivityForm: boolean; + showOptionInDevelopment = true; + + ngOnInit() { + this.showOptionInDevelopment = environment.production === EnvironmentType.TT_DEV; + } } diff --git a/src/app/modules/activities-management/store/activity-management.actions.spec.ts b/src/app/modules/activities-management/store/activity-management.actions.spec.ts index 9a0c8a48d..760ecc1d5 100644 --- a/src/app/modules/activities-management/store/activity-management.actions.spec.ts +++ b/src/app/modules/activities-management/store/activity-management.actions.spec.ts @@ -25,6 +25,24 @@ describe('LoadActivitiesSuccess', () => { expect(createActivityFail.type).toEqual(actions.ActivityManagementActionTypes.CREATE_ACTIVITY_FAIL); }); + it('ArchiveActivity type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY', () => { + const archiveActivity = new actions.ArchiveActivity('id_test'); + expect(archiveActivity.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY); + }); + + it('ArchiveActivitySuccess type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS', () => { + const archiveActivitySuccess = new actions.ArchiveActivitySuccess({ + id: 'id_test', + status: 'inactive' + }); + expect(archiveActivitySuccess.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS); + }); + + it('ArchiveActivityFail type is ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL', () => { + const archiveActivityFail = new actions.ArchiveActivityFail('error'); + expect(archiveActivityFail.type).toEqual(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL); + }); + it('UpdateActivitySuccess type is ActivityManagementActionTypes.UPDATE_ACTIVITY_SUCCESS', () => { const updateActivitySuccess = new actions.UpdateActivitySuccess({ id: '1', @@ -39,6 +57,24 @@ describe('LoadActivitiesSuccess', () => { expect(updateActivityFail.type).toEqual(actions.ActivityManagementActionTypes.UPDATE_ACTIVITY_FAIL); }); + it('UnarchiveActivity type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY', () => { + const unarchiveActivity = new actions.UnarchiveActivity('id_test'); + expect(unarchiveActivity.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY); + }); + + it('UnarchiveActivitySuccess type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS', () => { + const unarchiveActivitySuccess = new actions.UnarchiveActivitySuccess({ + id: 'id_test', + status: 'active' + }); + expect(unarchiveActivitySuccess.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS); + }); + + it('UnarchiveActivityFail type is ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL', () => { + const unarchiveActivityFail = new actions.UnarchiveActivityFail('error'); + expect(unarchiveActivityFail.type).toEqual(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL); + }); + it('SetActivityToEdit type is ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT', () => { const setActivityToEdit = new actions.SetActivityToEdit('123'); expect(setActivityToEdit.type).toEqual(actions.ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT); diff --git a/src/app/modules/activities-management/store/activity-management.actions.ts b/src/app/modules/activities-management/store/activity-management.actions.ts index f07d18efc..bf69a3c31 100644 --- a/src/app/modules/activities-management/store/activity-management.actions.ts +++ b/src/app/modules/activities-management/store/activity-management.actions.ts @@ -1,6 +1,6 @@ import { Action } from '@ngrx/store'; -import { Activity } from './../../shared/models/activity.model'; +import { Activity, ActivityStatus } from './../../shared/models/activity.model'; export enum ActivityManagementActionTypes { LOAD_ACTIVITIES = '[ActivityManagement] LOAD_ACTIVITIES', @@ -9,14 +9,18 @@ export enum ActivityManagementActionTypes { CREATE_ACTIVITY = '[ActivityManagement] CREATE_ACTIVITY', CREATE_ACTIVITY_SUCCESS = '[ActivityManagement] CREATE_ACTIVITY_SUCCESS', CREATE_ACTIVITY_FAIL = '[ActivityManagement] CREATE_ACTIVITY_FAIL', - DELETE_ACTIVITY = '[ActivityManagement] DELETE_ACTIVITY', - DELETE_ACTIVITY_SUCCESS = '[ActivityManagement] DELETE_ACTIVITY_SUCESS', - DELETE_ACTIVITY_FAIL = '[ActivityManagement] DELETE_ACTIVITY_FAIL', + ARCHIVE_ACTIVITY = '[ActivityManagement] ARCHIVE_ACTIVITY', + ARCHIVE_ACTIVITY_SUCCESS = '[ActivityManagement] ARCHIVE_ACTIVITY_SUCCESS', + ARCHIVE_ACTIVITY_FAIL = '[ActivityManagement] ARCHIVE_ACTIVITY_FAIL', UPDATE_ACTIVITY = '[ActivityManagement] UPDATE_ACTIVITY', UPDATE_ACTIVITY_SUCCESS = '[ActivityManagement] UPDATE_ACTIVITY_SUCCESS', UPDATE_ACTIVITY_FAIL = '[ActivityManagement] UPDATE_ACTIVITY_FAIL', + UNARCHIVE_ACTIVITY = '[ActivityManagement] UNARCHIVE_ACTIVITY', + UNARCHIVE_ACTIVITY_SUCCESS = '[ActivityManagement] UNARCHIVE_ACTIVITY_SUCCESS', + UNARCHIVE_ACTIVITY_FAIL = '[ActivityManagement] UNARCHIVE_ACTIVITY_FAIL', SET_ACTIVITY_ID_TO_EDIT = '[ActivityManagement] SET_ACTIVITY_ID_TO_EDIT', RESET_ACTIVITY_ID_TO_EDIT = '[ActivityManagement] RESET_ACTIVITY_ID_TO_EDIT', + DEFAULT_ACTIVITY = '[ActivityManagement] DEFAULT_ACTIVITY', } export class LoadActivities implements Action { @@ -26,78 +30,99 @@ export class LoadActivities implements Action { export class LoadActivitiesSuccess implements Action { public readonly type = ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS; - constructor(public payload: Activity[]) {} + constructor(public payload: Activity[]) { } } export class LoadActivitiesFail implements Action { public readonly type = ActivityManagementActionTypes.LOAD_ACTIVITIES_FAIL; - constructor(public error: string) {} + constructor(public error: string) { } } export class CreateActivity implements Action { public readonly type = ActivityManagementActionTypes.CREATE_ACTIVITY; - constructor(public payload: Activity) {} + constructor(public payload: Activity) { } } export class CreateActivitySuccess implements Action { public readonly type = ActivityManagementActionTypes.CREATE_ACTIVITY_SUCCESS; - constructor(public payload: Activity) {} + constructor(public payload: Activity) { } } export class CreateActivityFail implements Action { public readonly type = ActivityManagementActionTypes.CREATE_ACTIVITY_FAIL; - constructor(public error: string) {} + constructor(public error: string) { } } -export class DeleteActivity implements Action { - public readonly type = ActivityManagementActionTypes.DELETE_ACTIVITY; +export class ArchiveActivity implements Action { + public readonly type = ActivityManagementActionTypes.ARCHIVE_ACTIVITY; - constructor(public activityId: string) {} + constructor(public activityId: string) { } } -export class DeleteActivitySuccess implements Action { - public readonly type = ActivityManagementActionTypes.DELETE_ACTIVITY_SUCCESS; +export class ArchiveActivitySuccess implements Action { + public readonly type = ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS; - constructor(public activityId: string) {} + constructor(public payload: ActivityStatus) { } } -export class DeleteActivityFail implements Action { - public readonly type = ActivityManagementActionTypes.DELETE_ACTIVITY_FAIL; +export class ArchiveActivityFail implements Action { + public readonly type = ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL; - constructor(public error: string) {} + constructor(public error: string) { } } + export class UpdateActivity implements Action { public readonly type = ActivityManagementActionTypes.UPDATE_ACTIVITY; - constructor(public payload: Activity) {} + constructor(public payload: Activity) { } } export class UpdateActivitySuccess implements Action { public readonly type = ActivityManagementActionTypes.UPDATE_ACTIVITY_SUCCESS; - constructor(public payload: Activity) {} + constructor(public payload: Activity) { } } export class UpdateActivityFail implements Action { public readonly type = ActivityManagementActionTypes.UPDATE_ACTIVITY_FAIL; - constructor(public error: string) {} + constructor(public error: string) { } +} + +export class UnarchiveActivity implements Action { + public readonly type = ActivityManagementActionTypes.UNARCHIVE_ACTIVITY; + + constructor(public payload: string) { } +} +export class UnarchiveActivitySuccess implements Action { + public readonly type = ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS; + + constructor(public payload: ActivityStatus) { } +} +export class UnarchiveActivityFail implements Action { + public readonly type = ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL; + + constructor(public error: string) { } } export class SetActivityToEdit implements Action { public readonly type = ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT; - constructor(public payload: string) {} + constructor(public payload: string) { } } export class ResetActivityToEdit implements Action { public readonly type = ActivityManagementActionTypes.RESET_ACTIVITY_ID_TO_EDIT; } +export class DefaultActivities implements Action { + public readonly type = ActivityManagementActionTypes.DEFAULT_ACTIVITY; +} + export type ActivityManagementActions = | LoadActivities | LoadActivitiesSuccess @@ -105,11 +130,15 @@ export type ActivityManagementActions = | CreateActivity | CreateActivitySuccess | CreateActivityFail - | DeleteActivity - | DeleteActivitySuccess - | DeleteActivityFail + | ArchiveActivity + | ArchiveActivitySuccess + | ArchiveActivityFail | UpdateActivity | UpdateActivitySuccess | UpdateActivityFail + | UnarchiveActivity + | UnarchiveActivitySuccess + | UnarchiveActivityFail | SetActivityToEdit - | ResetActivityToEdit; + | ResetActivityToEdit + | DefaultActivities; diff --git a/src/app/modules/activities-management/store/activity-management.effects.spec.ts b/src/app/modules/activities-management/store/activity-management.effects.spec.ts index 8caa787ad..affcf0b52 100644 --- a/src/app/modules/activities-management/store/activity-management.effects.spec.ts +++ b/src/app/modules/activities-management/store/activity-management.effects.spec.ts @@ -15,7 +15,7 @@ describe('ActivityEffects', () => { let effects: ActivityEffects; let service: ActivityService; let toastrService; - const activity: Activity = { id: 'id', name: 'name', description: 'description', tenant_id: 'tenantId' }; + const activity: Activity = { id: 'id', name: 'name', description: 'description', tenant_id: 'tenantId', status: 'inactive' }; const activityList: Activity[] = []; beforeEach(() => { @@ -77,6 +77,29 @@ describe('ActivityEffects', () => { }); }); + it('action type is UNARCHIVE_ACTIVITY_SUCCESS when service is executed sucessfully', async () => { + const activityId = 'activityId'; + actions$ = of({ type: ActivityManagementActionTypes.UNARCHIVE_ACTIVITY, activityId }); + spyOn(service, 'updateActivity').and.returnValue(of(activity)); + spyOn(toastrService, 'success'); + + effects.unarchiveActivity$.subscribe((action) => { + expect(toastrService.success).toHaveBeenCalledWith(INFO_SAVED_SUCCESSFULLY); + expect(action.type).toEqual(ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS); + }); + }); + + it('action type is UNARCHIVE_ACTIVITY_FAIL when service fail in execution', async () => { + actions$ = of({ type: ActivityManagementActionTypes.UNARCHIVE_ACTIVITY, activity }); + spyOn(service, 'updateActivity').and.returnValue(throwError({ error: { message: 'fail!' } })); + spyOn(toastrService, 'error'); + + effects.unarchiveActivity$.subscribe((action) => { + expect(toastrService.error).toHaveBeenCalled(); + expect(action.type).toEqual(ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL); + }); + }); + it('action type is CREATE_ACTIVITY_SUCCESS when service is executed sucessfully', async () => { actions$ = of({ type: ActivityManagementActionTypes.CREATE_ACTIVITY, activity }); spyOn(service, 'createActivity').and.returnValue(of(activity)); @@ -99,27 +122,27 @@ describe('ActivityEffects', () => { }); }); - it('action type is DELETE_ACTIVITY_SUCCESS when service is executed sucessfully', async () => { + it('action type is ARCHIVE_ACTIVITY_SUCCESS when service is executed sucessfully', async () => { const activityId = 'activityId'; - actions$ = of({ type: ActivityManagementActionTypes.DELETE_ACTIVITY, activityId }); + actions$ = of({ type: ActivityManagementActionTypes.ARCHIVE_ACTIVITY, activityId }); spyOn(service, 'deleteActivity').and.returnValue(of({})); spyOn(toastrService, 'success'); - effects.deleteActivity$.subscribe((action) => { + effects.archiveActivity$.subscribe((action) => { expect(toastrService.success).toHaveBeenCalledWith(INFO_DELETE_SUCCESSFULLY); - expect(action.type).toEqual(ActivityManagementActionTypes.DELETE_ACTIVITY_SUCCESS); + expect(action.type).toEqual(ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS); }); }); - it('action type is DELETE_ACTIVITY_FAIL when service fail in execution', async () => { + it('action type is ARCHIVE_ACTIVITY_FAIL when service fail in execution', async () => { const activityId = 'activityId'; - actions$ = of({ type: ActivityManagementActionTypes.DELETE_ACTIVITY, activityId }); + actions$ = of({ type: ActivityManagementActionTypes.ARCHIVE_ACTIVITY, activityId }); spyOn(service, 'deleteActivity').and.returnValue(throwError({ error: { message: 'fail!' } })); spyOn(toastrService, 'error'); - effects.deleteActivity$.subscribe((action) => { + effects.archiveActivity$.subscribe((action) => { expect(toastrService.error).toHaveBeenCalled(); - expect(action.type).toEqual(ActivityManagementActionTypes.DELETE_ACTIVITY_FAIL); + expect(action.type).toEqual(ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL); }); }); }); diff --git a/src/app/modules/activities-management/store/activity-management.effects.ts b/src/app/modules/activities-management/store/activity-management.effects.ts index 88e8b1079..3b16dd15d 100644 --- a/src/app/modules/activities-management/store/activity-management.effects.ts +++ b/src/app/modules/activities-management/store/activity-management.effects.ts @@ -7,7 +7,7 @@ import { catchError, map, mergeMap } from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; import * as actions from './activity-management.actions'; -import { Activity } from './../../shared/models/activity.model'; +import { Activity, ActivityStatus } from './../../shared/models/activity.model'; import { ActivityService } from './../services/activity.service'; @Injectable() @@ -53,18 +53,21 @@ export class ActivityEffects { ); @Effect() - deleteActivity$: Observable = this.actions$.pipe( - ofType(actions.ActivityManagementActionTypes.DELETE_ACTIVITY), - map((action: actions.DeleteActivity) => action.activityId), - mergeMap((activityId) => - this.activityService.deleteActivity(activityId).pipe( + archiveActivity$: Observable = this.actions$.pipe( + ofType(actions.ActivityManagementActionTypes.ARCHIVE_ACTIVITY), + map((action: actions.ArchiveActivity) => ({ + id: action.activityId, + status: 'inactive' + })), + mergeMap((activity: ActivityStatus) => + this.activityService.deleteActivity(activity.id).pipe( map(() => { this.toastrService.success(INFO_DELETE_SUCCESSFULLY); - return new actions.DeleteActivitySuccess(activityId); + return new actions.ArchiveActivitySuccess(activity); }), catchError((error) => { this.toastrService.error(error.error.message); - return of(new actions.DeleteActivityFail(error)); + return of(new actions.ArchiveActivityFail(error)); }) ) ) @@ -87,4 +90,26 @@ export class ActivityEffects { ) ) ); + + @Effect() + unarchiveActivity$: Observable = this.actions$.pipe( + ofType(actions.ActivityManagementActionTypes.UNARCHIVE_ACTIVITY), + map((action: actions.UnarchiveActivity) => ({ + id: action.payload, + status: 'active' + }) + ), + mergeMap((activity: ActivityStatus) => + this.activityService.updateActivity(activity).pipe( + map((activityData) => { + this.toastrService.success(INFO_SAVED_SUCCESSFULLY); + return new actions.UnarchiveActivitySuccess(activityData); + }), + catchError((error) => { + this.toastrService.error(error.error.message); + return of(new actions.UnarchiveActivityFail(error)); + }) + ) + ) + ); } diff --git a/src/app/modules/activities-management/store/activity-management.reducers.spec.ts b/src/app/modules/activities-management/store/activity-management.reducers.spec.ts index 2f24b7b27..560e063a4 100644 --- a/src/app/modules/activities-management/store/activity-management.reducers.spec.ts +++ b/src/app/modules/activities-management/store/activity-management.reducers.spec.ts @@ -1,10 +1,10 @@ -import { Activity } from './../../shared/models/activity.model'; +import { Activity, ActivityStatus } from './../../shared/models/activity.model'; import * as actions from './activity-management.actions'; import { activityManagementReducer, ActivityState } from './activity-management.reducers'; describe('activityManagementReducer', () => { const initialState: ActivityState = { data: [], isLoading: false, message: '', activityIdToEdit: '' }; - const activity: Activity = { id: '1', name: 'Training', description: 'It is good for learning' }; + const activity: Activity = { id: '1', name: 'Training', description: 'It is good for learning', status: 'inactive' }; it('on LoadActivities, isLoading is true', () => { const action = new actions.LoadActivities(); @@ -56,27 +56,29 @@ describe('activityManagementReducer', () => { expect(state.isLoading).toEqual(false); }); - it('on DeleteActivity, isLoading is true', () => { + it('on ArchiveActivity, isLoading is true', () => { const activityToDeleteId = '1'; - const action = new actions.DeleteActivity(activityToDeleteId); + const action = new actions.ArchiveActivity(activityToDeleteId); const state = activityManagementReducer(initialState, action); - expect(state.isLoading).toEqual(true); + expect(state.isLoading).toBeTrue(); }); - it('on DeleteActivitySuccess, message equal to Activity removed successfully!', () => { - const currentState: ActivityState = { data: [activity], isLoading: false, message: '', activityIdToEdit: '' }; - const activityToDeleteId = '1'; - const action = new actions.DeleteActivitySuccess(activityToDeleteId); - + it('on ArchiveActivitySuccess, message equal to Activity archived successfully!', () => { + const currentActivity = { ...activity }; + currentActivity.status = 'active'; + const currentState: ActivityState = { data: [currentActivity], isLoading: false, message: '', activityIdToEdit: '' }; + const activityArchived: ActivityStatus = { id: '1', status: 'inactive' }; + const action = new actions.ArchiveActivitySuccess(activityArchived); const state = activityManagementReducer(currentState, action); - expect(state.data).toEqual([]); - expect(state.message).toEqual('Activity removed successfully!'); + + expect(state.data).toEqual([activity]); + expect(state.message).toEqual('Activity archived successfully!'); }); - it('on DeleteActivityFail, message equal to Something went wrong deleting activity!', () => { + it('on ArchiveActivityFail, message equal to Something went wrong deleting activity!', () => { const activityToDeleteId = '1'; - const action = new actions.DeleteActivityFail(activityToDeleteId); + const action = new actions.ArchiveActivityFail(activityToDeleteId); const state = activityManagementReducer(initialState, action); expect(state.isLoading).toEqual(false); @@ -112,19 +114,53 @@ describe('activityManagementReducer', () => { expect(state.isLoading).toEqual(false); }); - it('on SetActivityToEdit, should save the activityId to edit', () => { - const action = new actions.SetActivityToEdit(activity.id); + + it('on UnarchiveActivity, isLoading is true', () => { + const action = new actions.UnarchiveActivity('id_test'); const state = activityManagementReducer(initialState, action); - expect(state.activityIdToEdit).toEqual('1'); + expect(state.isLoading).toBeTrue(); }); - it('on ResetActivityToEdit, should clean the activityIdToEdit variable', () => { - const action = new actions.ResetActivityToEdit(); + it('on UnarchiveActivitySuccess, status activity is change to \"active\" in the store', () => { + const currentState: ActivityState = { data: [activity], isLoading: false, message: '', activityIdToEdit: '1' }; + const activityEdited: ActivityStatus = { id: '1', status: 'active' }; + const expectedActivity: Activity = { id: '1', name: 'Training', description: 'It is good for learning', status: 'active' }; + const action = new actions.UnarchiveActivitySuccess(activityEdited); + const state = activityManagementReducer(currentState, action); + + expect(state.data).toEqual([expectedActivity]); + expect(state.isLoading).toBeFalse(); + }); + + it('on UnarchiveActivityFail, message equal to \"Something went wrong unarchiving activities!\"', () => { + const action = new actions.UnarchiveActivityFail('error'); const state = activityManagementReducer(initialState, action); - expect(state.activityIdToEdit).toEqual(''); + expect(state.message).toEqual('Something went wrong unarchiving activities!'); + expect(state.isLoading).toBeFalse(); }); + + it('on SetActivityidToEdit, message equal to \"Set activityIdToEdit property\"', () => { + const action = new actions.SetActivityToEdit('1'); + const state = activityManagementReducer(initialState, action); + expect(state.message).toEqual('Set activityIdToEdit property'); + expect(state.isLoading).toBeFalse(); + }); + + it('on ResetActivityIdToEdit, message equal to \"Reset activityIdToEdit property\"', () => { + const action = new actions.ResetActivityToEdit(); + const state = activityManagementReducer(initialState, action); + expect(state.message).toEqual('Reset activityIdToEdit property'); + expect(state.isLoading).toBeFalse(); + }); + + it('on DefaultAction, state equal to initial state', () => { + const action = new actions.DefaultActivities(); + const state = activityManagementReducer(initialState, action); + expect(state.data).toEqual(initialState.data); + }); + }); diff --git a/src/app/modules/activities-management/store/activity-management.reducers.ts b/src/app/modules/activities-management/store/activity-management.reducers.ts index 2850dc268..d3478d7fe 100644 --- a/src/app/modules/activities-management/store/activity-management.reducers.ts +++ b/src/app/modules/activities-management/store/activity-management.reducers.ts @@ -63,29 +63,31 @@ export function activityManagementReducer(state: ActivityState = initialState, a }; } - case ActivityManagementActionTypes.DELETE_ACTIVITY: { + case ActivityManagementActionTypes.ARCHIVE_ACTIVITY: { return { ...state, isLoading: true, + message: 'Set activityIdToArchive property', }; } - case ActivityManagementActionTypes.DELETE_ACTIVITY_SUCCESS: { - const activities = state.data.filter((activity) => activity.id !== action.activityId); + case ActivityManagementActionTypes.ARCHIVE_ACTIVITY_SUCCESS: { + const index = activityList.findIndex((activity) => activity.id === action.payload.id); + activityList[index] = { ...activityList[index], ...action.payload }; + return { ...state, - data: activities, + data: activityList, isLoading: false, - message: 'Activity removed successfully!', + message: 'Activity archived successfully!', }; } - case ActivityManagementActionTypes.DELETE_ACTIVITY_FAIL: { + case ActivityManagementActionTypes.ARCHIVE_ACTIVITY_FAIL: { return { data: [], isLoading: false, message: 'Something went wrong deleting activity!', - activityIdToEdit: '', }; } @@ -118,6 +120,34 @@ export function activityManagementReducer(state: ActivityState = initialState, a }; } + case ActivityManagementActionTypes.UNARCHIVE_ACTIVITY: { + return { + ...state, + isLoading: true, + message: 'Set activityIdToUnarchive property', + }; + } + + case ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_SUCCESS: { + const index = activityList.findIndex((activity) => activity.id === action.payload.id); + activityList[index] = { ...activityList[index], ...action.payload }; + + return { + ...state, + data: activityList, + isLoading: false, + message: 'Unarchive activity successfully!', + }; + } + + case ActivityManagementActionTypes.UNARCHIVE_ACTIVITY_FAIL: { + return { + ...state, + isLoading: false, + message: 'Something went wrong unarchiving activities!', + }; + } + case ActivityManagementActionTypes.SET_ACTIVITY_ID_TO_EDIT: { return { ...state, diff --git a/src/app/modules/activities-management/store/activity-management.selectors.spec.ts b/src/app/modules/activities-management/store/activity-management.selectors.spec.ts index 79ee34b3e..c8c0e014a 100644 --- a/src/app/modules/activities-management/store/activity-management.selectors.spec.ts +++ b/src/app/modules/activities-management/store/activity-management.selectors.spec.ts @@ -1,7 +1,19 @@ +import { TestBed } from '@angular/core/testing'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; + import * as selectors from './activity-management.selectors'; +import { SpinnerOverlayComponent } from '../../shared/components/spinner-overlay/spinner-overlay.component'; + describe('ActivityManagement Selectors', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ MatProgressSpinnerModule ], + declarations: [ SpinnerOverlayComponent ], + }).compileComponents(); + }); + it('reads activityIdtoEdit from state', () => { const activityId = 'id'; const activityIdFound = selectors.activityIdToEdit.projector({ activityIdToEdit: activityId }); @@ -10,20 +22,73 @@ describe('ActivityManagement Selectors', () => { it('returns the activity with id that matches from the list', () => { const activityId = 'id'; - const activities = [{id: 'id', name: 'abc', description: 'xxx'}, - {id: '2', name: 'xyz', description: 'yyy'}]; + const activities = [{ id: 'id', name: 'abc', description: 'xxx' }, + { id: '2', name: 'xyz', description: 'yyy' }]; const activityFound = selectors.getActivityById.projector(activities, activityId); expect(activityFound).toEqual(activities[0]); }); it('should return all the data in the state when the selector allActivities is called', () => { - const activities = [{id: 'id', name: 'abc', description: 'xxx'}, - {id: '2', name: 'xyz', description: 'yyy'}]; - const activityState = {data: activities}; + const activities = [{ id: 'id', name: 'abc', description: 'xxx' }, + { id: '2', name: 'xyz', description: 'yyy' }]; + const activityState = { data: activities }; expect(selectors.allActivities.projector(activityState)).toBe(activities); }); + it('should return all the ordered data in the state when the selector allAtiveActivities is called', () => { + const activities = [ + { + id: '001', + name: 'Meeting', + description: 'Some description' + }, + { + id: '002', + name: 'ABC', + description: 'Some description' + }, + { + id: '003', + name: 'XYZ', + description: 'Some description' + }, + ]; + + const activitiesOrdered = [ + { + id: '002', + name: 'ABC', + description: 'Some description' + }, + { + id: '001', + name: 'Meeting', + description: 'Some description' + }, + { + id: '003', + name: 'XYZ', + description: 'Some description' + }, + ]; + + const activityState = { data: activities }; + + expect(selectors.allActiveActivities.projector(activityState)).toEqual(activitiesOrdered); + + }); + + it('should return all active data in the state when the selector allActiveActivities is called', () => { + const activities = [{ id: 'id', name: 'abc', description: 'xxx', status: 'active' }, + { id: '2', name: 'xyz', description: 'yyy', status: 'inactive' }, + { id: '3', name: 'xyzw', description: 'www', status: 'active' }]; + const activityState = { data: activities }; + const filteredActivities = activities.filter((item) => item.status === 'active'); + + expect(selectors.allActiveActivities.projector(activityState)).toEqual(filteredActivities); + }); + it('should select isLoading when the selector getIsLoading is called', () => { const isLoadingValue = true; const activityState = { isLoading: isLoadingValue }; diff --git a/src/app/modules/activities-management/store/activity-management.selectors.ts b/src/app/modules/activities-management/store/activity-management.selectors.ts index 5643ad4cf..cc20dfb9c 100644 --- a/src/app/modules/activities-management/store/activity-management.selectors.ts +++ b/src/app/modules/activities-management/store/activity-management.selectors.ts @@ -5,13 +5,17 @@ const getActivityState = createFeatureSelector('activities'); export const allActivities = createSelector(getActivityState, (state: ActivityState) => state?.data); +export const allActiveActivities = createSelector(getActivityState, (state: ActivityState) => { + return state?.data.filter((item) => item.status !== 'inactive').sort( (a, b) => { + return (a.name).localeCompare(b.name); + }); +}); + export const activityIdToEdit = createSelector(getActivityState, (state: ActivityState) => state?.activityIdToEdit); export const getActivityById = createSelector(allActivities, activityIdToEdit, (activities, activityId) => { if (activities && activityId) { - return activities.find((activity) => { - return activity.id === activityId; - }); + return activities.find((activity) => activity.id === activityId); } }); diff --git a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.html b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.html index 8279155e5..5acbefdb9 100644 --- a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.html +++ b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.html @@ -8,6 +8,7 @@ formControlName="name" placeholder="Customer name" [class.is-invalid]="customerForm.invalid && customerForm.touched" + (input)="onInputChangeCustomer($event.target.value)" required />
diff --git a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.spec.ts b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.spec.ts index df28e9d44..be1c8425a 100644 --- a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.spec.ts +++ b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.spec.ts @@ -63,11 +63,12 @@ describe('CreateCustomerComponent', () => { it('should call resetCustomerForm', () => { spyOn(component.customerForm, 'reset'); spyOn(component.closeCustomerComponent, 'emit'); - + spyOn(component.hasChangedEvent, 'emit'); component.resetCustomerForm(); expect(component.customerForm.reset).toHaveBeenCalled(); expect(component.closeCustomerComponent.emit).toHaveBeenCalledWith(false); + expect(component.hasChangedEvent.emit).toHaveBeenCalledWith(false); }); it('onSubmit, dispatch CreateCustomer and LoadCustomers actions', () => { @@ -140,4 +141,23 @@ describe('CreateCustomerComponent', () => { expect(component.changeValueAreTabsActives.emit).toHaveBeenCalledWith(component.areTabsActive); }); + it('if detect changes in customer information, it should emit a true', () => { + component.hasChange = true; + spyOn(component.hasChangedEvent, 'emit'); + + component.onInputChangeCustomer('changes text'); + + expect(component.hasChange).toBe(true); + expect(component.hasChangedEvent.emit).toHaveBeenCalledWith(component.hasChange); + }); + + it('if not detect changes in customer information, it should emit a false', () => { + component.hasChange = false; + spyOn(component.hasChangedEvent, 'emit'); + + component.onInputChangeCustomer(''); + + expect(component.hasChange).toBe(false); + expect(component.hasChangedEvent.emit).toHaveBeenCalledWith(component.hasChange); + }); }); diff --git a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.ts b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.ts index 9cb14ae8d..7900b02c1 100644 --- a/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.ts +++ b/src/app/modules/customer-management/components/customer-info/components/create-customer/create-customer.ts @@ -23,6 +23,8 @@ import { LoadCustomerProjects, CleanCustomerProjects } from '../../../projects/c export class CreateCustomerComponent implements OnInit, OnDestroy { customerForm: FormGroup; @Input() areTabsActive: boolean; + @Input() hasChange: boolean; + @Output() hasChangedEvent = new EventEmitter(); @Output() changeValueAreTabsActives = new EventEmitter(); @Output() closeCustomerComponent = new EventEmitter(); customerToEdit: Customer; @@ -78,6 +80,7 @@ export class CreateCustomerComponent implements OnInit, OnDestroy { this.markTabsAsInactive(); this.customerForm.reset(); } + this.hasChangedEvent.emit((this.hasChange = false)); } markTabsAsInactive() { @@ -93,5 +96,11 @@ export class CreateCustomerComponent implements OnInit, OnDestroy { this.customerForm.reset(); this.store.dispatch(new ResetCustomerToEdit()); this.closeCustomerComponent.emit(false); + this.hasChangedEvent.emit(this.hasChange = false); + } + + onInputChangeCustomer(searchValue: string): void { + return searchValue ? this.hasChangedEvent.emit(this.hasChange = true) : + this.hasChangedEvent.emit(this.hasChange = false); } } diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html index 09b7a0b76..33e9226b1 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.html @@ -8,34 +8,31 @@ > - Customer ID - Name - Options + Customer ID + Name + Options + Visibility - - + + - {{ customer.id }} - {{ customer.name }} - - + {{ customer.id }} + {{ customer.name }} + + + + @@ -48,8 +45,21 @@ tabindex="-1" role="dialog" aria-hidden="true" - [title]="'Delete Customer'" + [title]="'Disable Customer'" + [body]="message" + (closeModalEvent)="changeStatus()" +> + + + diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.scss b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.scss index e69de29bb..f8d73e904 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.scss +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.scss @@ -0,0 +1,28 @@ +table.dataTable thead th { + position: relative; +} + +table.dataTable thead th.sorting:after, +table.dataTable thead th.sorting_asc:after, +table.dataTable thead th.sorting_desc:after { + position: absolute; + top: 10px; + right: 3px; + display: block; + font-family: Arial, Helvetica, sans-serif; + color: #ff5e0a; + -webkit-text-stroke: 2px white; +} + +table.dataTable thead .sorting_asc::after { + content: "▲"; +} + +table.dataTable thead .sorting_desc::after { + content: "▼"; +} + +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc { + background-image: none; +} diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts index ce7548086..e022345bf 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.spec.ts @@ -8,12 +8,13 @@ import { CustomerState, DeleteCustomer, LoadCustomers, + ResetCustomerToEdit, SetCustomerToEdit, } from 'src/app/modules/customer-management/store'; import { DataTablesModule } from 'angular-datatables'; import { ActionsSubject } from '@ngrx/store'; -import { ResetProjectToEdit } from '../../../projects/components/store/project.actions'; -import { ResetProjectTypeToEdit } from '../../../projects-type/store'; +import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/components/store/project.actions'; +import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store'; describe('CustomerTableListComponent', () => { let component: CustomerListComponent; @@ -22,20 +23,43 @@ describe('CustomerTableListComponent', () => { const actionSub: ActionsSubject = new ActionsSubject(); const state = { - data: [{ tenant_id: 'id', name: 'name', description: 'description' }], + data: [{ tenant_id: 'id', name: 'name', description: 'description', status: 'inactive' }], isLoading: false, message: '', customerIdToEdit: '', customerId: '', }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [NgxPaginationModule, DataTablesModule], - declarations: [CustomerListComponent], - providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }], - }).compileComponents(); - })); + const btnProps = [ + { + key: 'active', + _status: false, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-success' + }, + { + key: 'inactive', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Inactive', + iconColor: 'text-danger' + }, + ]; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NgxPaginationModule, DataTablesModule], + declarations: [CustomerListComponent], + providers: [provideMockStore({ initialState: state }), { provide: ActionsSubject, useValue: actionSub }], + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(CustomerListComponent); @@ -58,13 +82,26 @@ describe('CustomerTableListComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new LoadCustomers()); }); - it('onClick edit, dispatch SetCustomerToEdit and enable customer form', () => { + it('Onclick Edit, if there are changes, the modal must be presented ', () => { + component.hasChange = true; + const expectMessage = 'You have unsaved changes, do you want to discard them?'; + + component.editCustomer('1'); + + expect(component.message).toEqual(expectMessage); + expect(component.showModal).toBeTrue(); + }); + + it('onClick edit, if there are no unsaved changes dispatch SetCustomerToEdit, enable customer form and hide modal', () => { + component.hasChange = false; + spyOn(store, 'dispatch'); component.editCustomer('1'); expect(store.dispatch).toHaveBeenCalledWith(new SetCustomerToEdit('1')); expect(component.showCustomerForm).toBeTruthy(); + expect(component.showModal).toBeFalse(); }); it('onClick edit, dispatch clean Forms in project and project type', () => { @@ -72,10 +109,35 @@ describe('CustomerTableListComponent', () => { component.editCustomer('1'); + expect(store.dispatch).toHaveBeenCalledWith(new SetProjectToEdit(null)); + expect(store.dispatch).toHaveBeenCalledWith(new SetProjectTypeToEdit(null)); expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectToEdit()); expect(store.dispatch).toHaveBeenCalledWith(new ResetProjectTypeToEdit()); }); + it('when you click close modal, modal should close, discard the current changes and load a new client to edit', () => { + spyOn(component.changeValueShowCustomerForm, 'emit'); + spyOn(store, 'dispatch'); + + component.editCustomer('1'); + component.closeModal(); + + expect(component.showModal).toBeFalse(); + expect(component.changeValueShowCustomerForm.emit).toHaveBeenCalledWith(true); + expect(store.dispatch).toHaveBeenCalledWith(new SetCustomerToEdit('1')); + }); + + it('when you click close modal, if the idToEdit is equal currentCustomerIdToEdit should dispatch ResetCustomerToEdit', () => { + + spyOn(store, 'dispatch'); + + component.idToEdit = '1'; + component.currentCustomerIdToEdit = '1'; + component.closeModal(); + + expect(store.dispatch).toHaveBeenCalledWith(new ResetCustomerToEdit()); + }); + it('onClick delete, dispatch DeleteCustomer', () => { spyOn(store, 'dispatch'); component.idToDelete = '1'; @@ -84,6 +146,17 @@ describe('CustomerTableListComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new DeleteCustomer('1')); }); + it('onClick delete, if idToDelete is equal to currentCustomerIdToEdit should dispatch ResetCustomerToEdit', () => { + + spyOn(store, 'dispatch'); + + component.idToDelete = '1'; + component.currentCustomerIdToEdit = '1'; + component.deleteCustomer(); + + expect(store.dispatch).toHaveBeenCalledWith(new ResetCustomerToEdit()); + }); + const params = [ { actionName: 'delete', actionType: CustomerManagementActionTypes.DELETE_CUSTOMER_SUCCESS }, { actionName: 'update', actionType: CustomerManagementActionTypes.UPDATE_CUSTOMER_SUCCESS }, @@ -125,7 +198,12 @@ describe('CustomerTableListComponent', () => { actionSubject.next(action); - expect(component.customers).toEqual(state.data); + const StateWithBtnProperties = state.data.map((customer) => { + const addProps = btnProps.find((prop) => prop.key === component.setActive(customer.status)); + return { ...customer, ...addProps }; + }); + + expect(component.customers).toEqual(StateWithBtnProperties); }); it('on success load customer, the datatable should be reloaded', async () => { @@ -141,6 +219,71 @@ describe('CustomerTableListComponent', () => { expect(component.dtElement.dtInstance.then).toHaveBeenCalled(); }); + it('openModal should set on true and display "Are you sure you want to disable customer"', () => { + const message = 'Are you sure you want to disable name?'; + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'active', + key: 'active', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnName: 'Archive', + }; + + component.openModal(itemData); + expect(component.showModal).toBeTrue(); + expect(component.message).toBe(message); + }); + + it('switchStatus should call openModal() on item.status = activate', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'activate', + key: 'activate', + _status: false, + btnColor: 'btn-danger', + btnIcon: 'fa-arrow-circle-down', + btnIconTwo: 'fa-check', + btnName: 'Archive', + iconColor: 'text-success' + }; + + spyOn(component, 'openModal'); + component.switchStatus(itemData); + expect(component.openModal).toHaveBeenCalled(); + }); + + it('switchStatus should set showModal false when item.status = inactive', () => { + const itemData = { + id: '1', + name: 'name', + description: 'description', + status: 'inactive', + key: 'inactive', + _status: true, + btnColor: 'btn-primary', + btnIcon: 'fa-arrow-circle-up', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-danger' + }; + + component.switchStatus(itemData); + expect(component.showModal).toBeFalse(); + }); + + + it('changeStatus should set inactive when active', () => { + component.changeStatus(); + expect(component.statusToEdit === 'inactive'); + }); + + afterEach(() => { component.dtTrigger.unsubscribe(); component.changeCustomerSubscription.unsubscribe(); diff --git a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts index e5c9785ed..6ab4dbf2d 100644 --- a/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts +++ b/src/app/modules/customer-management/components/customer-info/components/customer-list/customer-list.component.ts @@ -3,16 +3,28 @@ import { ActionsSubject, select, Store } from '@ngrx/store'; import { DataTableDirective } from 'angular-datatables'; import { Observable, Subject, Subscription } from 'rxjs'; import { delay, filter } from 'rxjs/operators'; -import { getIsLoading } from 'src/app/modules/customer-management/store/customer-management.selectors'; -import { Customer } from './../../../../../shared/models/customer.model'; +import { + customerIdtoEdit, + getIsLoading, +} from 'src/app/modules/customer-management/store/customer-management.selectors'; +import { Customer, CustomerUI } from './../../../../../shared/models/customer.model'; import { CustomerManagementActionTypes, DeleteCustomer, LoadCustomers, + ResetCustomerToEdit, SetCustomerToEdit, } from './../../../../store/customer-management.actions'; -import { ResetProjectToEdit } from '../../../projects/components/store/project.actions'; -import { ResetProjectTypeToEdit } from '../../../projects-type/store'; +import { ResetProjectToEdit, SetProjectToEdit } from '../../../projects/components/store/project.actions'; +import { ResetProjectTypeToEdit, SetProjectTypeToEdit } from '../../../projects-type/store'; +import { UnarchiveCustomer } from '../../../../store/customer-management.actions'; + + +export function scrollToCustomerForm(): void { + const element = document.getElementById('customerForm'); + element.scrollIntoView(); +} + @Component({ selector: 'app-customer-list', templateUrl: './customer-list.component.html', @@ -20,17 +32,24 @@ import { ResetProjectTypeToEdit } from '../../../projects-type/store'; }) export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { @Input() showCustomerForm: boolean; + @Input() hasChange: boolean; @Output() changeValueShowCustomerForm = new EventEmitter(); @Input() - customers: Customer[] = []; - dtOptions: any = {}; + customers: CustomerUI[] = []; + dtOptions: any = { + columnDefs: [{orderable: false, targets: [2]}] + }; dtTrigger: Subject = new Subject(); @ViewChild(DataTableDirective, { static: false }) dtElement: DataTableDirective; loadCustomersSubscription: Subscription; changeCustomerSubscription: Subscription; + customerIdToEditSubscription: Subscription; showModal = false; idToDelete: string; + idToEdit: string; + statusToEdit: string; + currentCustomerIdToEdit: string; message: string; isLoading$: Observable; @@ -39,15 +58,39 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { } ngOnInit(): void { - this.dtOptions = { - scrollY: '325px', - paging: false, - responsive: true, - }; + const btnProps = [ + { + key: 'active', + _status: false, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Active', + iconColor: 'text-success' + }, + { + key: 'inactive', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-check', + btnName: 'Inactive', + iconColor: 'text-danger' + }, + ]; + + const customerIdToEdit$ = this.store.pipe(select(customerIdtoEdit)); + this.customerIdToEditSubscription = customerIdToEdit$.subscribe((customerId: string) => { + this.currentCustomerIdToEdit = customerId; + }); + this.loadCustomersSubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === CustomerManagementActionTypes.LOAD_CUSTOMERS_SUCCESS)) .subscribe((action) => { - this.customers = action.payload; + this.customers = action.payload.map((customer: CustomerUI) => { + const addProps = btnProps.find((prop) => prop.key === this.setActive(customer.status)); + return { ...customer, ...addProps }; + }); this.rerenderDataTable(); }); this.changeCustomerSubscription = this.actionsSubject$ @@ -73,22 +116,44 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { ngOnDestroy() { this.loadCustomersSubscription.unsubscribe(); this.changeCustomerSubscription.unsubscribe(); + this.customerIdToEditSubscription.unsubscribe(); this.dtTrigger.unsubscribe(); } editCustomer(customerId: string) { + this.idToEdit = customerId; + if (this.hasChange) { + this.message = 'You have unsaved changes, do you want to discard them?'; + this.showModal = true; + } else { + this.showCustomerForm = true; + this.showModal = false; + this.changeValueShowCustomerForm.emit(this.showCustomerForm); + this.resetProjectFieldsToEdit(); + this.store.dispatch(new SetCustomerToEdit(customerId)); + } + } + + closeModal() { this.showCustomerForm = true; + this.showModal = false; this.changeValueShowCustomerForm.emit(this.showCustomerForm); - this.store.dispatch(new SetCustomerToEdit(customerId)); this.resetProjectFieldsToEdit(); + this.checkResetCustomerToEdit(this.idToEdit); + this.store.dispatch(new SetCustomerToEdit(this.idToEdit)); } private resetProjectFieldsToEdit() { + this.store.dispatch(new SetProjectTypeToEdit(null)); + this.store.dispatch(new SetProjectToEdit(null)); this.store.dispatch(new ResetProjectToEdit()); this.store.dispatch(new ResetProjectTypeToEdit()); } deleteCustomer() { + if (this.checkResetCustomerToEdit(this.idToDelete)) { + this.resetProjectFieldsToEdit(); + } this.store.dispatch(new DeleteCustomer(this.idToDelete)); this.showModal = false; } @@ -104,10 +169,44 @@ export class CustomerListComponent implements OnInit, OnDestroy, AfterViewInit { } } + private checkResetCustomerToEdit(id: string): boolean { + const isResetCustomerToEdit = this.currentCustomerIdToEdit === id; + if (isResetCustomerToEdit) { + this.store.dispatch(new ResetCustomerToEdit()); + } + return isResetCustomerToEdit; + } + openModal(item: Customer) { this.idToDelete = item.id; - this.message = `Are you sure you want to delete ${item.name}?`; + this.statusToEdit = item.status; + this.message = `Are you sure you want to disable ${item.name}?`; this.showModal = true; } + switchStatus(item: CustomerUI): void { + + if (item.key !== 'inactive') { + this.openModal(item); + } else { + this.showModal = false; + this.store.dispatch(new UnarchiveCustomer(item.id, this.changeOppositeStatus(item.key))); + } + } + + setActive(status: any): string { + return status === 'inactive' ? 'inactive' : 'active'; + } + changeOppositeStatus(status: string): string { + return status === 'inactive' ? 'active' : 'inactive'; + } + + changeStatus(): void { + this.store.dispatch(new UnarchiveCustomer(this.idToDelete, this.changeOppositeStatus(this.statusToEdit))); + } + + goToCustomerForm(){ + scrollToCustomerForm(); + } + } diff --git a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html index d80e31e81..749575fc1 100644 --- a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html +++ b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.html @@ -55,6 +55,7 @@
@@ -66,7 +67,7 @@ aria-labelledby="projects-type-tab" >
- +
@@ -78,7 +79,7 @@ aria-labelledby="projects-tab" >
- +
diff --git a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts index 697901557..bac181e7d 100644 --- a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts +++ b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.spec.ts @@ -1,8 +1,17 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ManagementCustomerProjectsComponent } from './management-customer-projects.component'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { of } from 'rxjs'; +import { MatNativeDateModule } from '@angular/material/core'; +import { ReactiveFormsModule } from '@angular/forms'; + import { CustomerState } from '../../store'; +import { CreateCustomerComponent } from '../customer-info/components/create-customer/create-customer'; +import { CreateProjectComponent } from '../projects/components/create-project/create-project.component'; +import { CreateProjectTypeComponent } from '../projects-type/components/create-project-type/create-project-type.component'; +import { ProjectListComponent } from '../projects/components/project-list/project-list.component'; +import { ProjectTypeListComponent } from '../projects-type/components/project-type-list/project-type-list.component'; +import { ManagementCustomerProjectsComponent } from './management-customer-projects.component'; + describe('ManagmentCustomerProjectsComponent', () => { let component: ManagementCustomerProjectsComponent; @@ -19,9 +28,17 @@ describe('ManagmentCustomerProjectsComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [ManagementCustomerProjectsComponent], + imports: [ MatNativeDateModule, ReactiveFormsModule], + declarations: [ + ManagementCustomerProjectsComponent, + CreateCustomerComponent, + CreateProjectComponent, + CreateProjectTypeComponent, + ProjectListComponent, + ProjectTypeListComponent, + ], providers: [ - provideMockStore({ initialState: state }) + provideMockStore({ initialState: state }), ], }).compileComponents(); })); @@ -38,6 +55,18 @@ describe('ManagmentCustomerProjectsComponent', () => { expect(component).toBeTruthy(); }); + it('set customerName on ngOnInit', () => { + spyOn(store, 'dispatch'); + spyOn(store, 'pipe').and.returnValue(of({ + id: 1, + name: 'project 4', + description: 'project 1 to test methos projectss', + status: 'inactive' + })); + component.ngOnInit(); + expect(component.customerName).toEqual('project 4'); + }); + it('should be enable tabs', () => { component.areTabsActive = false; component.activeTabs(true); @@ -64,9 +93,18 @@ describe('ManagmentCustomerProjectsComponent', () => { component.showTab('projects'); expect(component.activeTab).toEqual('projects'); }); + it('should call close customer function', () => { spyOn(component.closeCustemerForm, 'emit'); component.closeCustomer(false); expect(component.closeCustemerForm.emit).toHaveBeenCalledWith(false); }); + + it('with changes should emit a true ', () => { + spyOn(component.sendChanges, 'emit'); + + component.getChanges(true); + + expect(component.sendChanges.emit).toHaveBeenCalledWith(true); + }); }); diff --git a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.ts b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.ts index 83caacb26..511e75aff 100644 --- a/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.ts +++ b/src/app/modules/customer-management/components/management-customer-projects/management-customer-projects.component.ts @@ -10,7 +10,9 @@ import { Component, Output, EventEmitter, OnInit } from '@angular/core'; }) export class ManagementCustomerProjectsComponent implements OnInit { @Output() closeCustemerForm = new EventEmitter(); + @Output() sendChanges = new EventEmitter(); areTabsActive: boolean; + hasChanged: boolean; activeTab: string; customerName: string; @@ -42,4 +44,8 @@ export class ManagementCustomerProjectsComponent implements OnInit { this.activeTab = activeTab; } + getChanges($value: boolean) { + this.hasChanged = $value; + this.sendChanges.emit($value); + } } diff --git a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html index 69ef61d4e..6154be218 100644 --- a/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html +++ b/src/app/modules/customer-management/components/projects-type/components/create-project-type/create-project-type.component.html @@ -1,14 +1,26 @@
- +
Name is required.
- +
+ + + diff --git a/src/app/modules/reports/components/time-range-custom/time-range-custom.component.scss b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.scss new file mode 100644 index 000000000..5159b0883 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.scss @@ -0,0 +1,13 @@ +:host { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + } + + .date-range-form { + display: flex !important; + justify-content: space-between !important; + width: 100% !important; + } diff --git a/src/app/modules/reports/components/time-range-custom/time-range-custom.component.spec.ts b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.spec.ts new file mode 100644 index 000000000..b9f024567 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.spec.ts @@ -0,0 +1,139 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; +import { TimeRangeCustomComponent } from './time-range-custom.component'; +import { ToastrService } from 'ngx-toastr'; +import { EntryState } from 'src/app/modules/time-clock/store/entry.reducer'; +import * as entryActions from '../../../time-clock/store/entry.actions'; +import * as moment from 'moment'; +import { SimpleChange } from '@angular/core'; + +describe('TimeRangeCustomComponent', () => { + let component: TimeRangeCustomComponent; + let fixture: ComponentFixture; + let store: MockStore; + const toastrServiceStub = { + error: () => { + return 'test error'; + }, + }; + + const timeEntry = { + id: '1', + start_date: new Date(), + end_date: new Date(), + activity_id: '1', + technologies: ['react', 'redux'], + comments: 'any comment', + uri: 'TT-123', + project_id: '1', + }; + + const state = { + active: timeEntry, + entryList: [timeEntry], + isLoading: false, + message: 'test', + createError: false, + updateError: false, + timeEntriesSummary: null, + entriesForReport: [timeEntry], + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FormsModule, ReactiveFormsModule], + declarations: [TimeRangeCustomComponent], + providers: [provideMockStore({ initialState: state }), { provide: ToastrService, useValue: toastrServiceStub }], + }).compileComponents(); + store = TestBed.inject(MockStore); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimeRangeCustomComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('setInitialDataOnScreen on ngOnInit', () => { + spyOn(component, 'setInitialDataOnScreen'); + + component.ngOnInit(); + + expect(component.setInitialDataOnScreen).toHaveBeenCalled(); + }); + + it('LoadEntriesByTimeRange action is triggered when start date is before end date', () => { + const end = moment(new Date()).subtract(1, 'days'); + const start = moment(new Date()); + spyOn(store, 'dispatch'); + component.range.controls.start.setValue(end); + component.range.controls.end.setValue(start); + + component.onSubmit(); + + expect(store.dispatch).toHaveBeenCalledWith( + new entryActions.LoadEntriesByTimeRange({ + start_date: end.startOf('day'), + end_date: start.endOf('day'), + }) + ); + }); + + it('shows an error when the end date is before the start date', () => { + spyOn(toastrServiceStub, 'error'); + const yesterday = moment(new Date()).subtract(2, 'days'); + const today = moment(new Date()); + spyOn(store, 'dispatch'); + component.range.controls.start.setValue(today); + component.range.controls.end.setValue(yesterday); + + component.onSubmit(); + + expect(toastrServiceStub.error).toHaveBeenCalled(); + }); + + it('setInitialDataOnScreen sets dates in form', () => { + spyOn(component.range.controls.start, 'setValue'); + spyOn(component.range.controls.end, 'setValue'); + + component.setInitialDataOnScreen(); + + expect(component.range.controls.start.setValue).toHaveBeenCalled(); + expect(component.range.controls.end.setValue).toHaveBeenCalled(); + }); + + it('triggers onSubmit to set initial data', () => { + spyOn(component, 'onSubmit'); + + component.setInitialDataOnScreen(); + + expect(component.onSubmit).toHaveBeenCalled(); + }); + + it('When the ngOnChanges method is the first change, the onSubmit method is not called', () => { + spyOn(component, 'onSubmit'); + + component.ngOnChanges({ + userId: new SimpleChange(null, 'userId', true), + projectId: new SimpleChange(null, 'projectId', true), + activityId: new SimpleChange(null, 'activityId', true), + }); + + expect(component.onSubmit).not.toHaveBeenCalled(); + }); + + it('should call range form and delete variable local storage ', () => { + spyOn(localStorage, 'removeItem').withArgs('rangeDatePicker'); + component.range.setValue({ start: null, end: null }); + jasmine.clock().install(); + component.dateRangeChange(); + jasmine.clock().tick(200); + expect(localStorage.removeItem).toHaveBeenCalledWith('rangeDatePicker'); + jasmine.clock().uninstall(); + }); +}); diff --git a/src/app/modules/reports/components/time-range-custom/time-range-custom.component.ts b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.ts new file mode 100644 index 000000000..5c3f0cecc --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-custom.component.ts @@ -0,0 +1,77 @@ +import { formatDate } from '@angular/common'; +import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; +import { FormGroup, FormControl } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import * as moment from 'moment'; +import { ToastrService } from 'ngx-toastr'; +import { EntryState } from 'src/app/modules/time-clock/store/entry.reducer'; +import { DATE_FORMAT } from 'src/environments/environment'; +import * as entryActions from '../../../time-clock/store/entry.actions'; +import { TimeRangeHeaderComponent } from './time-range-header/time-range-header.component'; + +@Component({ + selector: 'app-time-range-custom', + templateUrl: './time-range-custom.component.html', + styleUrls: ['./time-range-custom.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TimeRangeCustomComponent implements OnInit, OnChanges { + @Input() userId: string; + @Input() projectId: string; + @Input() activityId: string; + customHeader = TimeRangeHeaderComponent; + range = new FormGroup({ + start: new FormControl(null), + end: new FormControl(null), + }); + + constructor(private store: Store, private toastrService: ToastrService) {} + + ngOnInit(): void { + this.setInitialDataOnScreen(); + } + + ngOnChanges(changes: SimpleChanges) { + const firstChange = Object.values(changes)[0].firstChange; + if (!firstChange) { + this.onSubmit(); + } + } + + setInitialDataOnScreen() { + this.range.setValue({ + start: formatDate(moment().startOf('isoWeek').format('l'), DATE_FORMAT, 'en'), + end: formatDate(moment().format('l'), DATE_FORMAT, 'en'), + }); + localStorage.setItem('rangeDatePicker', 'custom'); + this.onSubmit(); + } + + onSubmit() { + const startDate = moment(this.range.getRawValue().start).startOf('day'); + const endDate = moment(this.range.getRawValue().end).endOf('day'); + if (endDate.isBefore(startDate)) { + this.toastrService.error('The end date should be after the start date'); + } else { + this.store.dispatch( + new entryActions.LoadEntriesByTimeRange( + { + start_date: moment(this.range.getRawValue().start).startOf('day'), + end_date: moment(this.range.getRawValue().end).endOf('day'), + }, + this.userId, + this.projectId, + this.activityId + ) + ); + } + } + + dateRangeChange() { + setTimeout(() => { + if (this.range.get('start').value === null || this.range.get('end').value === null) { + localStorage.removeItem('rangeDatePicker'); + } + }, 200); + } +} diff --git a/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.html b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.html new file mode 100644 index 000000000..707d5328e --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.html @@ -0,0 +1,18 @@ + +
+ + + {{periodLabel}} + + +
diff --git a/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.scss b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.scss new file mode 100644 index 000000000..ab4206816 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.scss @@ -0,0 +1,16 @@ +.time-range-header { + display: flex; + align-items: center; + padding: 0.5em; +} + +.time-range-header-label { + flex: 1; + height: 1em; + font-weight: 500; + text-align: center; +} + +.time-range-double-arrow .mat-icon { + margin: -22%; +} diff --git a/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.spec.ts b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.spec.ts new file mode 100644 index 000000000..a4a57adc3 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.spec.ts @@ -0,0 +1,117 @@ +import { ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatCalendar, MatDateRangePicker } from '@angular/material/datepicker'; +import { By } from '@angular/platform-browser'; +import { of } from 'rxjs'; +import { MatIconModule } from '@angular/material/icon'; +import { IndividualConfig, ToastrService } from 'ngx-toastr'; +import { MatListModule } from '@angular/material/list'; + +import { TimeRangeHeaderComponent } from './time-range-header.component'; +import { TimeRangeOptionsComponent } from '../time-range-options/time-range-options.component'; + + +describe('TimeRangeHeaderComponent', () => { + let component: TimeRangeHeaderComponent; + let fixture: ComponentFixture>; + const value = { + stateChanges: { + pipe: () => { + return of({sucess: 'test'}); + } + }, + activeDate: new Date() + }; + + const toastrServiceStub = { + error: (message?: string, title?: string, override?: Partial) => { } + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatNativeDateModule, MatIconModule, MatListModule], + declarations: [ TimeRangeHeaderComponent, TimeRangeOptionsComponent ], + providers: [ + { provide: MatCalendar, useValue: value }, + { provide: MatDateRangePicker, useValue: {} }, + { provide: ToastrService, useValue: toastrServiceStub }, + ], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimeRangeHeaderComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should click previous year, previous month, next year and next month button with fakeAsync', fakeAsync(() => { + + const buttonsAll = [ + { + method: 'previousClicked', + values: [ + {style: '.time-range-double-arrow', call: 'year'}, + {style: '.time-range-month', call: 'month'} + ] + }, + { + method: 'nextClicked', + values: [ + {style: '.time-range-month-next', call: 'month'}, + {style: '.time-range-double-arrow-next', call: 'year'} + ] + }]; + buttonsAll.forEach((button: any) => { + spyOn(component, button.method); + button.values.forEach((val: any) => { + const buttonElement = fixture.debugElement.query(By.css(val.style)); + buttonElement.triggerEventHandler('click', null); + tick(); + expect(component[button.method]).toHaveBeenCalledWith(val.call); + }); + }); + })); + + it('should change the year with previousClicked method', () => { + component.calendar.activeDate = new Date(); + fixture.detectChanges(); + const makeDateYear = new Date(); + makeDateYear.setFullYear(makeDateYear.getFullYear() - 1); + component.previousClicked('year'); + expect(component.calendar.activeDate.toDateString()).toEqual(makeDateYear.toDateString()); + }); + + it('should change the month with previousClicked method', () => { + component.calendar.activeDate = new Date(); + fixture.detectChanges(); + const makeDateYear = new Date(); + makeDateYear.setMonth(makeDateYear.getMonth() - 1); + component.previousClicked('month'); + expect(component.calendar.activeDate.toDateString()).toEqual(makeDateYear.toDateString()); + }); + + it('should change the year with nextClicked method', () => { + component.calendar.activeDate = new Date(); + fixture.detectChanges(); + const makeDateYear = new Date(); + makeDateYear.setFullYear(makeDateYear.getFullYear() + 1); + component.nextClicked('year'); + expect(component.calendar.activeDate.toDateString()).toEqual(makeDateYear.toDateString()); + }); + + it('should change the month with nextClicked method', () => { + component.calendar.activeDate = new Date(); + fixture.detectChanges(); + const makeDateYear = new Date(); + makeDateYear.setMonth(makeDateYear.getMonth() + 1); + component.nextClicked('month'); + expect(component.calendar.activeDate.toDateString()).toEqual(makeDateYear.toDateString()); + }); + +}); diff --git a/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.ts b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.ts new file mode 100644 index 000000000..37611051b --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-header/time-range-header.component.ts @@ -0,0 +1,53 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Inject, + OnDestroy +} from '@angular/core'; +import {MatCalendar} from '@angular/material/datepicker'; +import {DateAdapter, MAT_DATE_FORMATS, MatDateFormats} from '@angular/material/core'; +import {Subject} from 'rxjs'; +import {takeUntil} from 'rxjs/operators'; + + +@Component({ + // selector: 'app-time-range-header', + styleUrls: ['./time-range-header.component.scss'], + templateUrl: './time-range-header.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TimeRangeHeaderComponent implements OnDestroy { + private destroyed = new Subject(); + + constructor( + public calendar: MatCalendar, public dateAdapter: DateAdapter, + @Inject(MAT_DATE_FORMATS) private dateFormats: MatDateFormats, cdr: ChangeDetectorRef) { + calendar.stateChanges + .pipe(takeUntil(this.destroyed)) + .subscribe(() => cdr.markForCheck()); + } + + ngOnDestroy() { + this.destroyed.next(); + this.destroyed.complete(); + } + + get periodLabel() { + return this.dateAdapter + .format(this.calendar.activeDate, this.dateFormats.display.monthYearLabel) + .toLocaleUpperCase(); + } + + previousClicked(mode: 'month' | 'year') { + this.calendar.activeDate = mode === 'month' ? + this.dateAdapter.addCalendarMonths(this.calendar.activeDate, -1) : + this.dateAdapter.addCalendarYears(this.calendar.activeDate, -1); + } + + nextClicked(mode: 'month' | 'year') { + this.calendar.activeDate = mode === 'month' ? + this.dateAdapter.addCalendarMonths(this.calendar.activeDate, 1) : + this.dateAdapter.addCalendarYears(this.calendar.activeDate, 1); + } +} diff --git a/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.html b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.html new file mode 100644 index 000000000..6cce60c1e --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.html @@ -0,0 +1,7 @@ +
+ + + {{item}} + + +
diff --git a/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.scss b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.scss new file mode 100644 index 000000000..76c75c9fb --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.scss @@ -0,0 +1,27 @@ +$width: 128px; + +:host { + position: absolute; + width: $width; + left: -$width; + } + + :host(.touch-ui) { + position: relative; + left: 0; + display: flex; + width: 100%; + } + +.list-time-range{ + background-color: white; + box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; +} + +.custom-items-time-range{ + padding-top: 0 !important; +} + +.custom-mat-list-option{ + height: 35px !important; +} diff --git a/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.spec.ts b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.spec.ts new file mode 100644 index 000000000..b05ee5590 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.spec.ts @@ -0,0 +1,138 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatDateRangePicker } from '@angular/material/datepicker'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatListModule } from '@angular/material/list'; +import { ToastrService } from 'ngx-toastr'; +import { TimeRangeOptionsComponent } from './time-range-options.component'; + + +describe('TimeRangeOptionsComponent', () => { + let component: TimeRangeOptionsComponent; + let fixture: ComponentFixture>; + const valueFunction = () => { + return ''; + }; + const toastrServiceStub = { + error: () => { + return 'error'; + } + }; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MatNativeDateModule, MatDialogModule, MatListModule], + declarations: [ TimeRangeOptionsComponent ], + providers: [ + { provide: MatDateRangePicker, useValue: {select: valueFunction, close: valueFunction} }, + { provide: ToastrService, useValue: toastrServiceStub }], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TimeRangeOptionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should click selectRange button and call calculateDateRange method', () => { + spyOn(component, 'calculateDateRange').and.returnValues(['', '']); + component.selectRange('today'); + expect(component.calculateDateRange).toHaveBeenCalled(); + }); + + it('should return current date when is called calculateDateRange method', () => { + const [start , end] = component.calculateDateRange('today'); + expect(new Date(start).toDateString()).toEqual(new Date().toDateString()); + expect(new Date(end).toDateString()).toEqual(new Date().toDateString()); + }); + + it('should return last 7 days when is called calculateDateRange method', () => { + const [start , end] = component.calculateDateRange('last 7 days'); + const lastDate = new Date(); + lastDate.setDate(new Date().getDate() - 6); + expect(new Date(start).toDateString()).toEqual(lastDate.toDateString()); + expect(new Date(end).toDateString()).toEqual(new Date().toDateString()); + }); + + it('should call getMondayCurrent, calculateMonth and calculateWeek method when is called calculateDateRange method', () => { + + const dataAll = [ + {method: 'getMondayCurrent', ranges: ['custom']}, + {method: 'calculateWeek', ranges: ['this week', 'last week']}, + {method: 'calculateMonth', ranges: ['this month', 'last month']} + ]; + + dataAll.forEach((val: any) => { + spyOn(component, val.method); + val.ranges.forEach((range: any) => { + component.calculateDateRange(range); + expect(component[val.method]).toHaveBeenCalled(); + }); + }); + }); + + it('should return time range last year or this year when is called calculateDateRange method', () => { + const currentYear = new Date().getFullYear(); + const dataAll = [ + {range: 'this year', values: + {start: {year: currentYear, month: 0, day: 1}, + end: {year: currentYear, month: 11, day: 31} + } + }, + {range: 'last year', + values: + {start: {year: currentYear - 1, month: 0, day: 1}, + end: {year: currentYear - 1, month: 11, day: 31} + } + }, + ]; + dataAll.forEach((val: any) => { + const [start, end] = component.calculateDateRange(val.range); + expect(new Date(start).toDateString()).toEqual( + new Date(val.values.start.year, val.values.start.month, val.values.start.day).toDateString()); + expect(new Date(end).toDateString()).toEqual( + new Date(val.values.end.year, val.values.end.month, val.values.end.day).toDateString()); + }); + }); + + it('should return a time range month when is called calculateMonth method', () => { + const currentYear = new Date().getFullYear(); + const currentMonth = new Date().getMonth(); + const currentDays = new Date(currentYear, currentMonth + 1, 0).getDate(); + const [start, end] = component.calculateMonth(new Date()); + expect(new Date(start).toDateString()).toEqual(new Date(currentYear, currentMonth, 1).toDateString()); + expect(new Date(end).toDateString()).toEqual(new Date(currentYear, currentMonth , currentDays).toDateString()); + }); + + it('should return a time range week when is called calculateWeek method', () => { + const currentYear = new Date().getFullYear(); + const currentMonth = new Date().getMonth(); + const firstDay = new Date().getDate() - new Date().getDay() + 1; + const lastDay = firstDay + 6; + const [start, end] = component.calculateWeek(new Date()); + expect(new Date(start).toDateString()).toEqual(new Date(currentYear, currentMonth, firstDay).toDateString()); + expect(new Date(end).toDateString()).toEqual(new Date(currentYear, currentMonth , lastDay).toDateString()); + }); + + it('shows an error when the date created is null from date adapter', () => { + spyOn(toastrServiceStub, 'error'); + spyOn(component.dateAdapter, 'today').and.returnValue(null); + component.getToday(); + expect(toastrServiceStub.error).toHaveBeenCalled(); + }); + + it('should call to method an error when the date created is null from date adapter', () => { + spyOn(component.dateAdapter, 'getYear').and.returnValues(2022); + spyOn(component.dateAdapter, 'getMonth').and.returnValues(7); + component.getMondayCurrent(); + expect(component.dateAdapter.getYear).toHaveBeenCalled(); + expect(component.dateAdapter.getMonth).toHaveBeenCalled(); + }); + +}); diff --git a/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.ts b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.ts new file mode 100644 index 000000000..3103b8507 --- /dev/null +++ b/src/app/modules/reports/components/time-range-custom/time-range-options/time-range-options.component.ts @@ -0,0 +1,140 @@ +import { Component, HostBinding, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { DateAdapter } from '@angular/material/core'; +import { MatDateRangePicker } from '@angular/material/datepicker'; +import { ToastrService } from 'ngx-toastr'; + + +const customPresets = [ + 'custom', + 'today', + 'last 7 days', + 'this week', + 'this month', + 'this year', + 'last week', + 'last month', + 'last year', +] as const; + +type CustomPreset = typeof customPresets[number]; +@Component({ + selector: 'app-time-range-options', + templateUrl: './time-range-options.component.html', + styleUrls: ['./time-range-options.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TimeRangeOptionsComponent implements OnInit{ + + customPresets = customPresets; + rangeDateSelected = ''; + @HostBinding('class.touch-ui') + readonly isTouchUi = this.picker.touchUi; + constructor( + public dateAdapter: DateAdapter, + public picker: MatDateRangePicker, + private toastrService: ToastrService + ) { + this.dateAdapter.getFirstDayOfWeek = () => 1; + } + + ngOnInit() { + this.rangeDateSelected = this.getLocalStorageRange(); + } + + getLocalStorageRange(): string { + return localStorage.getItem('rangeDatePicker'); + } + + setLocalStorageRange(range: string): void { + localStorage.setItem('rangeDatePicker', range); + } + + selectRange(rangeName: CustomPreset): void { + const [start, end] = this.calculateDateRange(rangeName); + this.setLocalStorageRange(rangeName); + + this.picker.select(start); + this.picker.select(end); + this.picker.close(); + } + + calculateDateRange(rangeName: CustomPreset): [start: Date, end: Date] { + const today = this.getToday(); + const year = this.dateAdapter.getYear(today); + + switch (rangeName) { + case 'custom': + const mondayWeek = this.getMondayCurrent(); + return [mondayWeek, today]; + case 'today': + return [today, today]; + case 'last 7 days': { + const start = this.dateAdapter.addCalendarDays(today, -6); + return [start, today]; + } + case 'this week': { + return this.calculateWeek(today); + } + case 'this month': { + return this.calculateMonth(today); + } + case 'this year': { + const start = this.dateAdapter.createDate(year, 0, 1); + const end = this.dateAdapter.createDate(year, 11, 31); + return [start, end]; + } + case 'last week': { + const thisDayLastWeek = this.dateAdapter.addCalendarDays(today, -7); + return this.calculateWeek(thisDayLastWeek); + } + case 'last month': { + const thisDayLastMonth = this.dateAdapter.addCalendarMonths(today, -1); + return this.calculateMonth(thisDayLastMonth); + } + case 'last year': { + const start = this.dateAdapter.createDate(year - 1, 0, 1); + const end = this.dateAdapter.createDate(year - 1, 11, 31); + return [start, end]; + } + } + } + + calculateMonth(forDay: Date): [start: Date, end: Date] { + const year = this.dateAdapter.getYear(forDay); + const month = this.dateAdapter.getMonth(forDay); + const start = this.dateAdapter.createDate(year, month, 1); + const end = this.dateAdapter.addCalendarDays( + start, + this.dateAdapter.getNumDaysInMonth(forDay) - 1 + ); + return [start, end]; + } + + calculateWeek(forDay: Date): [start: Date, end: Date] { + const deltaStart = + this.dateAdapter.getFirstDayOfWeek() - + this.dateAdapter.getDayOfWeek(forDay); + const start = this.dateAdapter.addCalendarDays(forDay, deltaStart); + const end = this.dateAdapter.addCalendarDays(start, 6); + return [start, end]; + } + + getToday(): Date { + const today = this.dateAdapter.today(); + if (today === null) { + this.toastrService.error('The end date should be after the start date'); + } + return today; + } + + getMondayCurrent(): Date { + const yearCurrent = this.dateAdapter.getYear(this.dateAdapter.today()); + const monthCurrent = this.dateAdapter.getMonth(this.dateAdapter.today()); + const today = new Date(); + const first = today.getDate() - today.getDay() + 1; + const monday = new Date(today.setDate(first)); + const mondayDayCurrent = monday.getDate(); + return this.dateAdapter.createDate(yearCurrent, monthCurrent, mondayDayCurrent); + } + +} diff --git a/src/app/modules/reports/components/time-range-form/time-range-form.component.ts b/src/app/modules/reports/components/time-range-form/time-range-form.component.ts index 87be577e0..09c2d37c6 100644 --- a/src/app/modules/reports/components/time-range-form/time-range-form.component.ts +++ b/src/app/modules/reports/components/time-range-form/time-range-form.component.ts @@ -1,36 +1,51 @@ import { ToastrService } from 'ngx-toastr'; import { formatDate } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import { OnChanges, SimpleChanges, Component, Input, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { DATE_FORMAT } from 'src/environments/environment'; import * as entryActions from '../../../time-clock/store/entry.actions'; import {Store} from '@ngrx/store'; import {EntryState} from '../../../time-clock/store/entry.reducer'; import * as moment from 'moment'; +import { DateAdapter } from '@angular/material/core'; @Component({ selector: 'app-time-range-form', templateUrl: './time-range-form.component.html', }) -export class TimeRangeFormComponent implements OnInit { +export class TimeRangeFormComponent implements OnInit, OnChanges { + + @Input() userId: string; + @Input() projectId: string; + @Input() activityId: string; + public reportForm: FormGroup; private startDate = new FormControl(''); private endDate = new FormControl(''); - constructor(private store: Store, private toastrService: ToastrService) { + constructor(private store: Store, private toastrService: ToastrService, private date: DateAdapter) { this.reportForm = new FormGroup({ startDate: this.startDate, endDate: this.endDate }); + date.getFirstDayOfWeek = () => 1; } + ngOnInit(): void { this.setInitialDataOnScreen(); } + ngOnChanges(changes: SimpleChanges) { + const firstChange = Object.values(changes)[0].firstChange; + if (!firstChange) { + this.onSubmit(); + } + } + setInitialDataOnScreen() { this.reportForm.setValue({ - startDate: formatDate(moment().startOf('week').toString(), DATE_FORMAT, 'en'), - endDate: formatDate(moment().endOf('week').toString(), DATE_FORMAT, 'en') + startDate: formatDate(moment().startOf('isoWeek').format('l'), DATE_FORMAT, 'en'), + endDate: formatDate(moment().format('l'), DATE_FORMAT, 'en') }); this.onSubmit(); } @@ -44,7 +59,7 @@ export class TimeRangeFormComponent implements OnInit { this.store.dispatch(new entryActions.LoadEntriesByTimeRange({ start_date: moment(this.startDate.value).startOf('day'), end_date: moment(this.endDate.value).endOf('day'), - })); + }, this.userId, this.projectId, this.activityId)); } } } diff --git a/src/app/modules/reports/components/time-range-form/time-range.component.spec.ts b/src/app/modules/reports/components/time-range-form/time-range.component.spec.ts index 8e78d3a00..58bb5f4bd 100644 --- a/src/app/modules/reports/components/time-range-form/time-range.component.spec.ts +++ b/src/app/modules/reports/components/time-range-form/time-range.component.spec.ts @@ -7,14 +7,17 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { InputDateComponent } from '../../../shared/components/input-date/input-date.component'; import * as entryActions from '../../../time-clock/store/entry.actions'; import * as moment from 'moment'; +import { SimpleChange } from '@angular/core'; +import { DateAdapter } from '@angular/material/core'; describe('Reports Page', () => { describe('TimeRangeFormComponent', () => { let component: TimeRangeFormComponent; let fixture: ComponentFixture; let store: MockStore; + const toastrServiceStub = { - error: (message?: string, title?: string, override?: Partial) => { } + error: (message?: string, title?: string, override?: Partial) => {}, }; const timeEntry = { @@ -25,7 +28,7 @@ describe('Reports Page', () => { technologies: ['react', 'redux'], comments: 'any comment', uri: 'custom uri', - project_id: '123' + project_id: '123', }; const state = { @@ -39,18 +42,20 @@ describe('Reports Page', () => { entriesForReport: [timeEntry], }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [FormsModule, ReactiveFormsModule], - declarations: [TimeRangeFormComponent, InputDateComponent], - providers: [ - provideMockStore({ initialState: state }), - { provide: ToastrService, useValue: toastrServiceStub } - ], - }).compileComponents(); - store = TestBed.inject(MockStore); - - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [FormsModule, ReactiveFormsModule], + declarations: [TimeRangeFormComponent, InputDateComponent], + providers: [ + provideMockStore({ initialState: state }), + { provide: ToastrService, useValue: toastrServiceStub }, + { provide: DateAdapter, useClass: DateAdapter }, + ], + }).compileComponents(); + store = TestBed.inject(MockStore); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(TimeRangeFormComponent); @@ -71,10 +76,12 @@ describe('Reports Page', () => { component.onSubmit(); - expect(store.dispatch).toHaveBeenCalledWith(new entryActions.LoadEntriesByTimeRange({ - start_date: yesterday.startOf('day'), - end_date: today.endOf('day') - })); + expect(store.dispatch).toHaveBeenCalledWith( + new entryActions.LoadEntriesByTimeRange({ + start_date: yesterday.startOf('day'), + end_date: today.endOf('day'), + }) + ); }); it('setInitialDataOnScreen on ngOnInit', () => { @@ -114,6 +121,28 @@ describe('Reports Page', () => { expect(component.onSubmit).toHaveBeenCalled(); }); + it('triggers onSubmit with the form status valid', () => { + const valid = component.reportForm.valid; + spyOn(component, 'onSubmit'); + + component.setInitialDataOnScreen(); + + expect(valid).toBeTruthy(); + expect(component.onSubmit).toHaveBeenCalled(); + }); + + it('When the ngOnChanges method is the first change, the onSubmit method is not called', () => { + spyOn(component, 'onSubmit'); + + component.ngOnChanges({ + userId: new SimpleChange(null, 'user_id', true), + projectId: new SimpleChange(null, 'project_id', true), + activityId: new SimpleChange(null, 'activity_id', true), + }); + + expect(component.onSubmit).not.toHaveBeenCalled(); + }); + afterEach(() => { fixture.destroy(); }); diff --git a/src/app/modules/reports/models/total-hours-report.ts b/src/app/modules/reports/models/total-hours-report.ts new file mode 100644 index 000000000..fc5bd6774 --- /dev/null +++ b/src/app/modules/reports/models/total-hours-report.ts @@ -0,0 +1,12 @@ +export class TotalHours { + + hours: number; + minutes: number; + seconds: number; + + constructor() { + this.hours = 0; + this.minutes = 0; + this.seconds = 0; + } +} diff --git a/src/app/modules/reports/pages/reports.component.html b/src/app/modules/reports/pages/reports.component.html index 661823546..931c46752 100644 --- a/src/app/modules/reports/pages/reports.component.html +++ b/src/app/modules/reports/pages/reports.component.html @@ -1,3 +1,2 @@ - - - + + \ No newline at end of file diff --git a/src/app/modules/reports/pages/reports.component.spec.ts b/src/app/modules/reports/pages/reports.component.spec.ts index 4a131f56e..958791abd 100644 --- a/src/app/modules/reports/pages/reports.component.spec.ts +++ b/src/app/modules/reports/pages/reports.component.spec.ts @@ -27,9 +27,15 @@ describe('ReportsComponent', () => { fixture.detectChanges(); const compile = fixture.debugElement.nativeElement; - const reportForm = compile.querySelector('app-time-range-form'); + const reportForm = compile.querySelector('app-time-range-custom'); const reportDataTable = compile.querySelector('app-time-entries-table'); expect(reportForm).toBeTruthy(); expect(reportDataTable).toBeTruthy(); })); + + it(`Given the id of the user 'abc123' this is assigned to the variable userId`, () => { + const userId = 'abc123'; + component.user(userId); + expect(component.userId).toEqual('abc123'); + }); }); diff --git a/src/app/modules/reports/pages/reports.component.ts b/src/app/modules/reports/pages/reports.component.ts index dbdc3e14c..1885b8c53 100644 --- a/src/app/modules/reports/pages/reports.component.ts +++ b/src/app/modules/reports/pages/reports.component.ts @@ -6,4 +6,18 @@ import { Component } from '@angular/core'; styleUrls: ['./reports.component.scss'] }) export class ReportsComponent { + + userId: string; + projectId: string; + activityId: string; + + user(userId: string){ + this.userId = userId; + } + activity(activityId: string){ + this.activityId = activityId; + } + project(projectId: string){ + this.projectId = projectId; + } } diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.html b/src/app/modules/shared/components/dark-mode/dark-mode.component.html new file mode 100644 index 000000000..df5a11423 --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.html @@ -0,0 +1,10 @@ + diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts new file mode 100644 index 000000000..c85f6a56f --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.spec.ts @@ -0,0 +1,103 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; +import { SocialAuthService } from 'angularx-social-login'; +import { of } from 'rxjs'; +import { delay } from 'rxjs/operators'; +import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service'; +import { FeatureToggleModel } from '../../feature-toggles/feature-toggle.model'; +import { FeatureFilterModel } from '../../feature-toggles/filters/feature-filter.model'; +import { DarkModeComponent } from './dark-mode.component'; + + +// since the backend is not in Azure, this module is not used +xdescribe('DarkModeComponent', () => { + let component: DarkModeComponent; + let fixture: ComponentFixture; + let html: HTMLElement; + let featureToggleGeneralService: FeatureToggleGeneralService; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DarkModeComponent], + imports: [HttpClientTestingModule], + providers: [{ provide: SocialAuthService, useValue: socialAuthServiceStub }] + }).compileComponents(); + }); + + const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); + beforeEach(() => { + fixture = TestBed.createComponent(DarkModeComponent); + component = fixture.componentInstance; + html = document.documentElement; + featureToggleGeneralService = TestBed.inject(FeatureToggleGeneralService); + fixture.detectChanges(); + }); + + afterEach(() => { + localStorage.removeItem('theme'); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); + + it('should be light the default theme', () => { + expect(component.theme).toEqual('light'); + }); + + it('should change the value of the theme property if it exists in the local storage', () => { + localStorage.setItem('theme', 'dark'); + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('dark'); + }); + + it('should be light theme if it does not exist in local storage', () => { + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('light'); + }); + + it('should be light mode if property ‘theme’ does not have a value in local storage', () => { + localStorage.setItem('theme', ''); + component.checkThemeInLocalStorage(); + expect(component.theme).toEqual('light'); + }); + + it('should switch to dark theme if the theme property is light and vice versa', () => { + component.theme = component.setTheme(); + expect(component.theme).toEqual('dark'); + component.theme = component.setTheme(); + expect(component.theme).toEqual('light'); + }); + + it('should add the dark class in the html tag to apply dark mode', () => { + component.theme = 'dark'; + component.addOrRemoveDarkMode(); + fixture.detectChanges(); + expect(html.classList.contains('dark')).toBe(true); + }); + + it('should not have dark class in the html tag when theme is light', () => { + component.addOrRemoveDarkMode(); + fixture.detectChanges(); + expect(component.theme).toEqual('light'); + expect(html.classList.contains('dark')).toBe(false); + }); + + it('should change the value of the theme property, save it in the local storage and add the dark class to the HTML tag to change the theme', () => { + component.changeToDarkOrLightTheme(); + fixture.detectChanges(); + expect(component.theme).toEqual('dark'); + expect(localStorage.getItem('theme')).toEqual('dark'); + expect(html.classList.contains('dark')).toBe(true); + }); + + it('should be true the isFeatureToggleDarkModeActive property when the user has the dark-mode feature toggle enabled', fakeAsync(() => { + const filters: FeatureFilterModel[] = []; + const featureToggle: FeatureToggleModel = {name: 'dark-mode', enabled: true, filters}; + spyOn(featureToggleGeneralService, 'getActivated').and.returnValue(of([featureToggle]).pipe(delay(1))); + component.ngOnInit(); + tick(1); + expect(component.isFeatureToggleDarkModeActive).toBeTruthy(); + })); + +}); diff --git a/src/app/modules/shared/components/dark-mode/dark-mode.component.ts b/src/app/modules/shared/components/dark-mode/dark-mode.component.ts new file mode 100644 index 000000000..cd649c54a --- /dev/null +++ b/src/app/modules/shared/components/dark-mode/dark-mode.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { FeatureToggle } from 'src/environments/enum'; +import { FeatureToggleGeneralService } from '../../feature-toggles/feature-toggle-general/feature-toggle-general.service'; + +@Component({ + selector: 'app-dark-mode', + templateUrl: './dark-mode.component.html', +}) +export class DarkModeComponent implements OnInit { + public theme = 'light'; + public isFeatureToggleDarkModeActive: boolean; + + constructor( + private featureToggleGeneralService: FeatureToggleGeneralService + ) {} + + ngOnInit() { + this.featureToggleGeneralService.getActivated().subscribe((featuresToggles) => { + const darkModeToggle = featuresToggles.find( (item) => item.name === FeatureToggle.DARK_MODE); + this.isFeatureToggleDarkModeActive = darkModeToggle?.enabled; + if (this.isFeatureToggleDarkModeActive) { + this.checkThemeInLocalStorage(); + this.addOrRemoveDarkMode(); + } + }); + } + + getLocalStorageTheme(): string { + return localStorage.getItem('theme') || 'light'; + } + + setLocalStorageTheme(theme: string): void { + localStorage.setItem('theme', theme); + } + + checkThemeInLocalStorage(): void { + if ('theme' in localStorage) { + this.theme = this.getLocalStorageTheme(); + } else { + this.setLocalStorageTheme(this.theme); + } + } + + isDarkTheme(): boolean { + return this.theme === 'dark' ? true : false; + } + + setTheme(): string { + return this.isDarkTheme() ? 'light' : 'dark'; + } + + addOrRemoveDarkMode(): void { + if (this.isDarkTheme()) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + } + + changeToDarkOrLightTheme(): void { + this.theme = this.setTheme(); + this.setLocalStorageTheme(this.theme); + this.addOrRemoveDarkMode(); + } +} diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.html b/src/app/modules/shared/components/details-fields/details-fields.component.html index 5a04f4b87..77fc6d3e6 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.html +++ b/src/app/modules/shared/components/details-fields/details-fields.component.html @@ -12,33 +12,26 @@
- - + - -
-
- - - + +
+
+ {{item.customer.name}} - + {{item.name}} +
-
- + + - -
-
@@ -55,7 +48,7 @@ formControlName="activity_id" > - +
-
- -
+
+ +
+
@@ -99,25 +101,19 @@ [format]="24" formControlName="start_hour" id="start_hour" - [disabled]="!(project_name.value && project_id.value)" class="timepicker-input" + [defaultTime]="'00:00'" + [class.timepicker-input--disabled]="!(project_id.value && project_name.value)" >
-
- +
+
- + + {{ this.getTimeDifference() }} +
@@ -126,36 +122,36 @@ [format]="24" formControlName="end_hour" id="end_hour" - [disabled]="!(project_name.value && project_id.value)" class="timepicker-input" - > + [defaultTime]="'00:00'" + [class.timepicker-input--disabled]="!(project_id.value && project_name.value)" + >
+ maxlength="1500" + formControlName="description" + placeholder="Write a brief description of your activity" + class="form-control" + id="NotesTextarea" + rows="3" + >
diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.scss b/src/app/modules/shared/components/details-fields/details-fields.component.scss index 88b00d6e7..30255b5e9 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.scss +++ b/src/app/modules/shared/components/details-fields/details-fields.component.scss @@ -103,3 +103,30 @@ input[type="date"]::-webkit-clear-button { border-radius: 0.25rem; } +.timepicker-input--disabled ::ng-deep div{ + pointer-events: none; + background-color: #e9ecef; + opacity: 1; +} + +.border-tag { + border: 1px solid $primary; + padding: 10px; + border-radius: 5px; + margin-top: 10px; +} + +::ng-deep .cdk-overlay-container { + z-index: 1100 !important; +} + +.url-ticket-input { + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +::ng-deep .ngx-timepicker { + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 2px; +} diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts index 60139632f..d5f137e4c 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts +++ b/src/app/modules/shared/components/details-fields/details-fields.component.spec.ts @@ -6,6 +6,13 @@ import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AutocompleteLibModule } from 'angular-ng-autocomplete'; import * as moment from 'moment'; import { IndividualConfig, ToastrService } from 'ngx-toastr'; +import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { CalendarModule, DateAdapter } from 'angular-calendar'; +import { adapterFactory } from 'angular-calendar/date-adapters/date-fns'; +import { MatNativeDateModule } from '@angular/material/core'; +import { NgSelectModule } from '@ng-select/ng-select'; + import { getCreateError, getUpdateError } from 'src/app/modules/time-clock/store/entry.selectors'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; import { getCustomerProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; @@ -18,10 +25,10 @@ import { TechnologiesComponent } from './../technologies/technologies.component' import { DetailsFieldsComponent } from './details-fields.component'; import { ProjectSelectedEvent } from './project-selected-event'; import { SaveEntryEvent } from './save-entry-event'; -import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; - import { DATE_FORMAT } from 'src/environments/environment'; import { DATE_FORMAT_YEAR } from 'src/environments/environment'; +import { Project } from '../../models'; + describe('DetailsFieldsComponent', () => { type Merged = TechnologyState & ProjectState & EntryState; @@ -36,14 +43,15 @@ describe('DetailsFieldsComponent', () => { let formValues; const actionSub: ActionsSubject = new ActionsSubject(); const toastrServiceStub = { - error: (message?: string, title?: string, override?: Partial) => { }, - warning: (message?: string, title?: string, override?: Partial) => { } + error: (message?: string, title?: string, override?: Partial) => {}, + warning: (message?: string, title?: string, override?: Partial) => {}, }; const state = { projects: { - projects: [{ id: 'id', name: 'name', project_type_id: '' }], + projects: [{ id: 'id', name: 'name', project_type_id: '', customer: { name: 'Juan', description: 'sadsa' } }], customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [{ id: 'id', name: 'name', customer: { name: 'Juan' } }], isLoading: false, message: '', projectToEdit: undefined, @@ -53,7 +61,30 @@ describe('DetailsFieldsComponent', () => { isLoading: false, }, activities: { - data: [{ id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc' }], + data: [ + { id: 'fc5fab41-a21e-4155-9d05-511b956ebd05', tenant_id: 'ioet', deleted: null, name: 'abc', status: 'active' }, + { + id: 'fc5fab41-a21e-4155-9d05-511b956ebd07', + tenant_id: 'ioet_1', + deleted: null, + name: 'def', + status: 'active', + }, + { + id: 'fc5fab41-a21e-4155-9d05-511b956ebd08', + tenant_id: 'ioet_2', + deleted: null, + name: 'ghi', + status: 'inactive', + }, + { + id: 'fc5fab41-a21e-4155-9d05-511b956ebd09', + tenant_id: 'ioet_3', + deleted: null, + name: 'jkl', + status: 'active', + }, + ], isLoading: false, message: 'Data fetch successfully!', activityIdToEdit: '', @@ -79,22 +110,50 @@ describe('DetailsFieldsComponent', () => { const mockCurrentDate = '2020-12-01T12:00:00'; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [DetailsFieldsComponent, TechnologiesComponent], - providers: [ - provideMockStore({ initialState: state }), - { provide: ActionsSubject, useValue: actionSub }, - { provide: ToastrService, useValue: toastrServiceStub } - ], - imports: [FormsModule, ReactiveFormsModule, AutocompleteLibModule, NgxMaterialTimepickerModule], - }).compileComponents(); - store = TestBed.inject(MockStore); - mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies); - mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); - mockEntriesUpdateErrorSelector = store.overrideSelector(getUpdateError, state.Entries.updateError); - mockEntriesCreateErrorSelector = store.overrideSelector(getCreateError, state.Entries.createError); - })); + const entryWithoutRequiredFields = { + project_id: 'p1', + project_name: 'ioet inc.', + activity_id: 'a1', + uri: '', + start_date: '2020-02-05', + end_date: '2020-02-05', + start_hour: '00:00', + end_hour: '00:01', + description: '', + technology: '', + }; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [DetailsFieldsComponent, TechnologiesComponent], + providers: [ + provideMockStore({ initialState: state }), + { provide: ActionsSubject, useValue: actionSub }, + { provide: ToastrService, useValue: toastrServiceStub }, + MatDatepickerModule + ], + imports: [ + FormsModule, + ReactiveFormsModule, + AutocompleteLibModule, + NgxMaterialTimepickerModule, + NgSelectModule, + MatDatepickerModule, + MatNativeDateModule, + CalendarModule.forRoot({ + provide: DateAdapter, + useFactory: adapterFactory, + }), + ], + }).compileComponents(); + store = TestBed.inject(MockStore); + mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies); + mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); + mockEntriesUpdateErrorSelector = store.overrideSelector(getUpdateError, state.Entries.updateError); + mockEntriesCreateErrorSelector = store.overrideSelector(getCreateError, state.Entries.createError); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(DetailsFieldsComponent); @@ -107,7 +166,7 @@ describe('DetailsFieldsComponent', () => { end_date: null, description: '', technologies: [], - id: 'xyz' + id: 'xyz', }; formValues = { project_id: 'id', @@ -116,8 +175,8 @@ describe('DetailsFieldsComponent', () => { uri: 'ticketUri', start_date: '', end_date: '', - start_hour: '00:00:10', - end_hour: '00:00:11', + start_hour: '00:00', + end_hour: '00:00', description: '', technology: '', }; @@ -129,18 +188,28 @@ describe('DetailsFieldsComponent', () => { }); it('onClearedComponent project id and name are set to empty', () => { - component.onClearedComponent(null); + const search = { term: '' }; + component.onClearedComponent(search); expect(component.project_id.value).toBe(''); expect(component.project_name.value).toBe(''); }); + it('should change the listProjectsShowed to listProjects if search is not empty on onClearedComponent', () => { + const search = { term: 'Ioet Inc.' }; + const listProjects: Project[] = [{ id: '1', name: 'abc', status: 'active' }]; + component.listProjects = listProjects; + component.onClearedComponent(search); + + expect(component.listProjectsShowed).toBe(component.listProjects); + }); + it('onSelectedProject project id and name are set using event data', () => { spyOn(component.entryForm, 'patchValue'); - component.onSelectedProject( {id: 'id', search_field: 'foo'} ); + component.onSelectedProject({ id: 'id', search_field: 'foo' }); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( { project_id: 'id', project_name: 'foo', } ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ project_id: 'id', project_name: 'foo' }); }); it('if form is invalid then saveEntry is not emited', () => { @@ -154,7 +223,7 @@ describe('DetailsFieldsComponent', () => { [ { actionType: EntryActionTypes.CREATE_ENTRY_SUCCESS }, { actionType: EntryActionTypes.UPDATE_ENTRY_SUCCESS }, - ].map((param) => { + ].forEach((param) => { it(`cleanForm after an action type ${param.actionType} is received`, () => { const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; const action = { @@ -169,11 +238,47 @@ describe('DetailsFieldsComponent', () => { }); }); + it('on cleanFieldsForm the project_id and project_name should be kept', () => { + const entryFormValueExpected = { + project_id: '', + project_name: '', + activity_id: '', + uri: '', + start_date: formatDate(new Date(), DATE_FORMAT, 'en'), + end_date: formatDate(new Date(), DATE_FORMAT, 'en'), + start_hour: '00:00', + end_hour: '00:00', + description: '', + technology: '', + }; + + component.entryForm.setValue(formValues); + component.cleanFieldsForm(); + + expect(component.entryForm.value).toEqual(entryFormValueExpected); + }); + it('should emit ngOnChange without data', () => { component.entryToEdit = null; + const formValue = { + project_id: '', + project_name: '', + activity_id: '', + uri: '', + start_date: formatDate(new Date(), DATE_FORMAT, 'en'), + end_date: formatDate(new Date(), DATE_FORMAT, 'en'), + start_hour: '00:00', + end_hour: '00:00', + description: '', + technology: '', + }; component.ngOnChanges(); expect(component.shouldRestartEntry).toBeFalse(); - expect(component.entryForm.value).toEqual(initialData); + expect(component.entryForm.value).toEqual(formValue); + component.activities$.subscribe((item) => { + expect(item.length).not.toBe(null); + expect(item.length).toBe(3); + }); }); it('should emit ngOnChange with new data', () => { @@ -196,6 +301,31 @@ describe('DetailsFieldsComponent', () => { expect(component.entryForm.value).toEqual(formValue); }); + const activitiesParams = [ + { select_activity_id: 'fc5fab41-a21e-4155-9d05-511b956ebd07', expected_size_activities: 3, title: 'active' }, + { select_activity_id: 'fc5fab41-a21e-4155-9d05-511b956ebd08', expected_size_activities: 4, title: 'inactive' }, + ]; + activitiesParams.forEach((param) => { + it(`should emit ngOnChange to set ${param.expected_size_activities} activities for select (${param.title} time entry clicked)`, () => { + component.entryToEdit = { ...entryToEdit, activity_id: param.select_activity_id }; + spyOn(component.entryForm, 'patchValue'); + component.ngOnChanges(); + + component.activities$.subscribe((items) => { + expect(items.length).toBe(param.expected_size_activities); + }); + }); + }); + + it('selectActiveActivities should return 3 active activities', () => { + const activeActivities = component.selectActiveActivities(); + + activeActivities.subscribe((item) => { + expect(item.length).not.toBe(null); + expect(item.length).toBe(3); + }); + }); + it('should call createError ', () => { const childComponent = jasmine.createSpyObj('ChildComponent', ['closeModal']); component.closeModal = childComponent; @@ -223,8 +353,8 @@ describe('DetailsFieldsComponent', () => { uri: '', start_date: '2020-02-05', end_date: '2020-02-05', - start_hour: '00:00:01', - end_hour: '00:01:01', + start_hour: '00:00', + end_hour: '00:01', description: '', technology: '', }); @@ -237,12 +367,12 @@ describe('DetailsFieldsComponent', () => { activity_id: 'a1', technologies: [], description: '', - start_date: new Date('2020-02-05T00:00:01').toISOString(), - end_date: new Date('2020-02-05T00:01:01').toISOString(), + start_date: new Date('2020-02-05T00:00:00').toISOString(), + end_date: new Date('2020-02-05T00:01:00').toISOString(), uri: '', timezone_offset: new Date().getTimezoneOffset(), }, - shouldRestartEntry: false + shouldRestartEntry: false, }; expect(component.saveEntry.emit).toHaveBeenCalledWith(data); @@ -277,7 +407,10 @@ describe('DetailsFieldsComponent', () => { component.onGoingToWorkOnThisChange({ currentTarget: { checked: false } }); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( { end_date: '2020-12-30', end_hour: '09:45', } ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ + end_date: '2020-12-30', + end_hour: formatDate(new Date(), 'HH:mm', 'en'), + }); }); it('when creating a new entry, then the new entry should be marked as not run', () => { @@ -333,69 +466,43 @@ describe('DetailsFieldsComponent', () => { activity_id: 'a1', technologies: [], description: '', - start_date: new Date('2020-06-11T00:00:10').toISOString(), + start_date: new Date('2020-06-11T00:00:00').toISOString(), uri: 'ticketUri', timezone_offset: new Date().getTimezoneOffset(), }, - shouldRestartEntry: false + shouldRestartEntry: false, }; expect(component.saveEntry.emit).toHaveBeenCalledWith(data); }); - it('should not modify the start_date when start_hour has not been modified', () => { - const currentDate = moment().format('YYYY-MM-DD'); - const startHour = moment().subtract(3, 'hours').format('HH:mm:ss'); - const expectedStartDate = new Date(`${currentDate}T${startHour.trim()}`); - - component.entryToEdit = {...entryToEdit, start_date: expectedStartDate }; - fixture.componentInstance.ngOnChanges(); - - component.entryForm.patchValue({ description: 'test' }); - - expect(component.dateToSubmit('start_date', 'start_hour')).toEqual(expectedStartDate); - }); - it('should modify the start_date when start_hour has been modified', () => { const startDate = new Date(mockCurrentDate); - component.entryToEdit = {...entryToEdit, start_date: startDate }; + component.entryToEdit = { ...entryToEdit, start_date: startDate }; fixture.componentInstance.ngOnChanges(); const updatedStartDate = moment(startDate).subtract(1, 'hours'); - const updatedStartHour = updatedStartDate.format('HH:mm'); - component.entryForm.patchValue({start_hour: updatedStartHour}); + const updatedStartHour = updatedStartDate.format('HH:mm'); + component.entryForm.patchValue({ start_hour: updatedStartHour }); const expectedStartDate = moment(updatedStartDate).seconds(0).millisecond(0).toISOString(); expect(component.dateToSubmit('start_date', 'start_hour')).toEqual(expectedStartDate); - }); - - it('should not modify the end_date when end_hour has not been modified', () => { - const currentDate = moment().format('YYYY-MM-DD'); - const endHour = moment().subtract(3, 'hours').format('HH:mm:ss'); - const expectedEndDate = new Date(`${currentDate}T${endHour.trim()}`); - - component.entryToEdit = {...entryToEdit, end_date: expectedEndDate }; - fixture.componentInstance.ngOnChanges(); - - component.entryForm.patchValue({ description: 'test' }); - - expect(component.dateToSubmit('end_date', 'end_hour')).toEqual(expectedEndDate); }); it('should modify the end_date when end_hour has been modified', () => { const endDate = new Date(mockCurrentDate); - component.entryToEdit = {...entryToEdit, end_date: endDate }; + component.entryToEdit = { ...entryToEdit, end_date: endDate }; fixture.componentInstance.ngOnChanges(); const updatedEndDate = moment(endDate).subtract(1, 'hours'); - const updatedEndHour = updatedEndDate.format('HH:mm'); - component.entryForm.patchValue({end_hour: updatedEndHour}); + const updatedEndHour = updatedEndDate.format('HH:mm'); + component.entryForm.patchValue({ end_hour: updatedEndHour }); const expectedEndDate = moment(updatedEndDate).seconds(0).millisecond(0).toISOString(); expect(component.dateToSubmit('end_date', 'end_hour')).toEqual(expectedEndDate); - }); + }); it('displays error message when the date selected is in the future', () => { spyOn(toastrServiceStub, 'error'); @@ -432,17 +539,266 @@ describe('DetailsFieldsComponent', () => { it('should emit projectSelected event', () => { spyOn(component.projectSelected, 'emit'); const item = { - id : 'id', - search_field : 'TimeTracker' + id: 'id', + search_field: 'TimeTracker', }; component.onSelectedProject(item); const data: ProjectSelectedEvent = { - projectId: 'id' + projectId: 'id', }; expect(component.projectSelected.emit).toHaveBeenCalledWith(data); }); + it('on get current date should return expected date', () => { + const expectedDate = moment(new Date()).format(DATE_FORMAT_YEAR); + + expect(component.getCurrentDate()).toEqual(expectedDate); + }); + + it('on the input with id #start_date we could get the id and max value', () => { + fixture.detectChanges(); + const expectedDate = moment(new Date()).format(DATE_FORMAT_YEAR); + const startDateInput: HTMLInputElement = fixture.debugElement.nativeElement.querySelector( + `input[id="start_date"],input[max="${component.getCurrentDate()}"]` + ); + + expect(startDateInput.id).toEqual('start_date'); + expect(startDateInput.max).toEqual(expectedDate); + }); + + const diffParams = [ + { + case: 'positive should return correctly diff', + entryDates: { + start_date: '2021-04-15', + end_date: '2021-04-15', + start_hour: '18:05', + end_hour: '19:00', + }, + expectedTimeDiff: '00:55', + }, + { + case: 'negative should return 00:00', + entryDates: { + start_date: '2021-04-15', + end_date: '2021-04-14', + start_hour: '18:05', + end_hour: '17:00', + }, + expectedTimeDiff: '00:00', + }, + ]; + diffParams.forEach((param) => { + it(`if [start_date, start_hour] and [end_date, end_hour] diff is ${param.case}`, () => { + component.entryForm.setValue({ ...formValues, ...param.entryDates }); + const timeDiff = component.getTimeDifference(); + + expect(timeDiff).toBe(param.expectedTimeDiff); + }); + }); + + it('should find an activity with given id & status: inactive', () => { + const expectedActivity = { + id: 'fc5fab41-a21e-4155-9d05-511b956ebd08', + tenant_id: 'ioet_2', + deleted: null, + name: 'ghi', + status: 'inactive', + }; + + component.entryToEdit = { ...entryToEdit, activity_id: 'fc5fab41-a21e-4155-9d05-511b956ebd08' }; + spyOn(component.entryForm, 'patchValue'); + + const foundActivity = component.findInactiveActivity(state.activities.data); + expect(foundActivity).toEqual(expectedActivity); + }); + + const datesParams = [ + { + case: 'should return true when the start time entry is greater than the end time', + entryDates: { + start_date: '2021-04-21', + end_date: '2021-04-21', + start_hour: '20:00', + end_hour: '08:00', + }, + expected_result: true, + }, + { + case: 'should return false when the start time entry is not greater than the end time', + entryDates: { + start_date: '2021-04-21', + end_date: '2021-04-21', + start_hour: '19:00', + end_hour: '20:00', + }, + expected_result: false, + }, + ]; + datesParams.forEach((param) => { + it(`${param.case}`, () => { + component.entryForm.setValue({ ...formValues, ...param.entryDates }); + const result = component.isStartTimeEntryAfterEndedEntry(); + + expect(result).toBe(param.expected_result); + }); + }); + + it('should display an error message when isStartTimeEntryAfterEndedEntry() is true & goingToWorkOnThis is false', () => { + const times = { + start_date: '2021-04-21', + end_date: '2021-04-21', + start_hour: '10:00', + end_hour: '00:00', + }; + component.goingToWorkOnThis = false; + component.entryForm.setValue({ ...formValues, ...times }); + const displayError = component.isStartTimeEntryAfterEndedEntry() && !component.goingToWorkOnThis; + component.onSubmit(); + expect(displayError).toBeTrue(); + }); + + it('should show Date out field on details field component on time entry page when another entry is running', () => { + const times = { + start_date: '2021-04-21', + end_date: '2021-04-21', + start_hour: '10:00', + end_hour: '12:00', + }; + component.goingToWorkOnThis = true; + spyOn(toastrServiceStub, 'error'); + + const fixtureToTest = TestBed.createComponent(DetailsFieldsComponent); + const componentToTest = fixtureToTest.componentInstance; + componentToTest.entryForm.setValue({ ...formValues, ...times }); + componentToTest.onSubmit(); + expect(toastrServiceStub.error).not.toHaveBeenCalled(); + }); + + it('Should return a date in ISO format given a date, hour, minute, second and milisecond', () => { + const fakeDates = [ + { + date: '2021-04-20', + hourAndMinutes: '10:00', + seconds: 20, + miliseconds: 100, + }, + { + date: '2020-09-21', + hourAndMinutes: '08:00', + seconds: 10, + miliseconds: 100, + }, + { + date: '2021-04-15', + hourAndMinutes: '23:00', + seconds: 0, + miliseconds: 0, + }, + { + date: '2019-03-29', + hourAndMinutes: '12:00', + seconds: 30, + miliseconds: 400, + }, + ]; + + const expectedISODates = [ + new Date('2021-04-20T10:00:20.100').toISOString(), + new Date('2020-09-21T08:00:10.100').toISOString(), + new Date('2021-04-15T23:00:00.000').toISOString(), + new Date('2019-03-29T12:00:30.400').toISOString(), + ]; + + fakeDates.forEach(({ date, hourAndMinutes, seconds, miliseconds }, fakeDateIndex) => { + const dateInISOFormat = component.getDateISOFormat(date, hourAndMinutes, seconds, miliseconds); + expect(dateInISOFormat).toBe(expectedISODates[fakeDateIndex]); + }); + }); + + it('should return a number in ISO format given a normal number', () => { + const numbersForTest = [1, 2, 3, 4, 20, 30, 40, 32, 45]; + + const expectedISOFormatNumbers = ['01', '02', '03', '04', '20', '30', '40', '32', '45']; + + numbersForTest.forEach((currentNumber, numberIndex) => { + const numberinISOFormat = component.getNumberInISOFormat(currentNumber); + expect(numberinISOFormat).toBe(expectedISOFormatNumbers[numberIndex]); + }); + }); + + it('when the user selects technologies, set them in the variable selectedTechnologies', () => { + const techs = ['php', 'angular']; + component.onTechnologiesUpdated(techs); + expect(component.selectedTechnologies).toEqual(techs); + }); + + it('when the user does not select a project, display a warning message.', () => { + spyOn(toastrServiceStub, 'warning'); + component.onclickFormAction(true); + expect(toastrServiceStub.warning).toHaveBeenCalled(); + }); + + it('if entry is set to project_name search_field is assigned in entryForm', () => { + const listProjects: Project[] = [{ id: 'id', name: 'abc', status: 'active', search_field: 'name' }]; + component.listProjects = listProjects; + component.entryToEdit = { ...entryToEdit }; + component.ngOnChanges(); + expect(component.entryForm.value.project_name).toBe('name'); + }); + + it('should display a warning message when trying to save time entry of internal app without required fields', () => { + component.entryForm.setValue(entryWithoutRequiredFields); + spyOn(toastrServiceStub, 'warning'); + + component.onSubmit(); + expect(toastrServiceStub.warning).toHaveBeenCalled(); + }); + + it('should not display a warning message when trying to save time entry of internal app with uri and save', () => { + component.entryForm.setValue({ ...entryWithoutRequiredFields, uri: 'TTL-886' }); + spyOn(toastrServiceStub, 'warning'); + spyOn(component.saveEntry, 'emit'); + + component.onSubmit(); + expect(toastrServiceStub.warning).not.toHaveBeenCalled(); + expect(component.saveEntry.emit).toHaveBeenCalled(); + }); + + it('should not display a warning message when trying to save time entry of external customer without required fields and save', () => { + component.entryForm.setValue({ ...entryWithoutRequiredFields, project_name: 'Warby Parker' }); + spyOn(component.saveEntry, 'emit'); + spyOn(toastrServiceStub, 'warning'); + + component.onSubmit(); + expect(toastrServiceStub.warning).not.toHaveBeenCalled(); + + expect(component.saveEntry.emit).toHaveBeenCalled(); + }); + + it('should not display a warning message when trying to save time entry of internal app with description and save', () => { + component.entryForm.setValue({ ...entryWithoutRequiredFields, description: 'Description' }); + spyOn(component.saveEntry, 'emit'); + spyOn(toastrServiceStub, 'warning'); + + component.onSubmit(); + expect(toastrServiceStub.warning).not.toHaveBeenCalled(); + expect(component.saveEntry.emit).toHaveBeenCalled(); + }); + + /* We allow saving time entries with empty fields in uri and description for safari books and english lessons */ + it('should not display a warning message when trying to save time entry of English Lessons without description and save', () => { + component.entryForm.setValue({ ...entryWithoutRequiredFields, project_name: 'ioet inc. - English Lessons' }); + spyOn(toastrServiceStub, 'warning'); + spyOn(component.saveEntry, 'emit'); + + component.onSubmit(); + expect(toastrServiceStub.warning).not.toHaveBeenCalled(); + + expect(component.saveEntry.emit).toHaveBeenCalled(); + }); + /* TODO As part of https://github.com/ioet/time-tracker-ui/issues/424 a new parameter was added to the details-field-component, and now these couple of tests are failing. A solution to this error might be generate a Test Wrapper Component. More details here: diff --git a/src/app/modules/shared/components/details-fields/details-fields.component.ts b/src/app/modules/shared/components/details-fields/details-fields.component.ts index 5a0434d8a..e41801a7f 100644 --- a/src/app/modules/shared/components/details-fields/details-fields.component.ts +++ b/src/app/modules/shared/components/details-fields/details-fields.component.ts @@ -4,12 +4,17 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ActionsSubject, select, Store } from '@ngrx/store'; import * as moment from 'moment'; import { ToastrService } from 'ngx-toastr'; -import { filter } from 'rxjs/operators'; +import { filter, map, mergeMap } from 'rxjs/operators'; import { getCreateError, getUpdateError } from 'src/app/modules/time-clock/store/entry.selectors'; -import { ActivityState, allActivities, LoadActivities } from '../../../activities-management/store'; +import { + ActivityState, + allActiveActivities, + allActivities, + LoadActivities, +} from '../../../activities-management/store'; import * as projectActions from '../../../customer-management/components/projects/components/store/project.actions'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; -import { getProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; +import { getProjects, getRecentProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; import * as entryActions from '../../../time-clock/store/entry.actions'; import { EntryState } from '../../../time-clock/store/entry.reducer'; import { Activity, Entry, Project } from '../../models'; @@ -17,8 +22,13 @@ import { TechnologyState } from '../../store/technology.reducers'; import { EntryActionTypes } from './../../../time-clock/store/entry.actions'; import { SaveEntryEvent } from './save-entry-event'; import { ProjectSelectedEvent } from './project-selected-event'; -import { get } from 'lodash'; -import { DATE_FORMAT } from 'src/environments/environment'; +import { get, isEmpty } from 'lodash'; +import { DATE_FORMAT, DATE_FORMAT_YEAR } from 'src/environments/environment'; +import { TechnologiesComponent } from '../technologies/technologies.component'; +import { MatDatepicker } from '@angular/material/datepicker'; +import { Observable } from 'rxjs'; +import { EMPTY_FIELDS_ERROR_MESSAGE } from '../../messages'; +import { INTERNAL_APP_STRING, PROJECT_NAME_TO_SKIP } from 'src/app/modules/shared/internal-app-constants'; type Merged = TechnologyState & ProjectState & ActivityState & EntryState; @Component({ @@ -27,19 +37,22 @@ type Merged = TechnologyState & ProjectState & ActivityState & EntryState; styleUrls: ['./details-fields.component.scss'], }) export class DetailsFieldsComponent implements OnChanges, OnInit { - keyword = 'search_field'; @Input() entryToEdit: Entry; @Input() canMarkEntryAsWIP: boolean; @Output() saveEntry = new EventEmitter(); @Output() projectSelected = new EventEmitter(); @ViewChild('closeModal') closeModal: ElementRef; + @ViewChild('technologies', { static: true }) technologies: TechnologiesComponent; entryForm: FormGroup; selectedTechnologies: string[] = []; isLoading = false; listProjects: Project[] = []; - activities: Activity[] = []; + listRecentProjects: Project[] = []; + listProjectsShowed: Project[] = []; + activities$: Observable; goingToWorkOnThis = false; shouldRestartEntry = false; + isTechnologiesDisabled = true; constructor( private formBuilder: FormBuilder, @@ -69,17 +82,15 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { this.listProjects = []; projects.forEach((project) => { const projectWithSearchField = { ...project }; - projectWithSearchField.search_field = `${project.customer_name} - ${project.name}`; + projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; this.listProjects.push(projectWithSearchField); }); } }); + this.getRecentProjects(); this.store.dispatch(new LoadActivities()); - const activities$ = this.store.pipe(select(allActivities)); - activities$.subscribe((response) => { - this.activities = response; - }); + this.activities$ = this.selectActiveActivities(); const updateError$ = this.store.pipe(select(getUpdateError)); updateError$.subscribe((updateError) => { @@ -109,25 +120,69 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { }); } - onClearedComponent(event) { - this.entryForm.patchValue({ - project_id: '', - project_name: '', - }); + onClearedComponent({ term }) { + const isSearchEmpty = term === ''; + if (isSearchEmpty) { + this.isTechnologiesDisabled = true; + this.entryForm.patchValue({ + project_id: '', + project_name: '', + }); + } + this.listProjectsShowed = isSearchEmpty ? this.listRecentProjects : this.listProjects; } onSelectedProject(item) { - this.projectSelected.emit({ projectId: item.id }); - this.entryForm.patchValue({ - project_id: item.id, - project_name: item.search_field, + if (item) { + this.isTechnologiesDisabled = false; + this.projectSelected.emit({ projectId: item.id }); + this.entryForm.patchValue({ + project_id: item.id, + project_name: item.search_field, + }); + this.listProjectsShowed = this.listRecentProjects; + } + } + + onStartDateChange($event: string) { + this.end_date.setValue($event); + } + + getTimeDifference(): string { + const startDate = moment(`${this.start_date.value} ${this.start_hour.value}`); + const endDate = moment(`${this.end_date.value} ${this.end_hour.value}`); + if (startDate <= endDate) { + const diffDate = endDate.diff(startDate); + const duration = moment.duration(diffDate); + return moment.utc(duration.asMilliseconds()).format('HH:mm'); + } + return '00:00'; + } + + getRecentProjects(): void { + this.store.dispatch(new projectActions.LoadProjects()); + const recentProjects$ = this.store.pipe(select(getRecentProjects)); + recentProjects$.subscribe((projects) => { + this.listRecentProjects = []; + if (projects?.length > 0) { + projects.forEach((project) => { + const projectWithSearchField = { ...project }; + projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; + this.listRecentProjects.push(projectWithSearchField); + }); + } else { + this.listRecentProjects = this.listProjects; + } + this.listProjectsShowed = this.listRecentProjects; }); } ngOnChanges(): void { - this.goingToWorkOnThis = this.entryToEdit ? this.entryToEdit.running : false; + this.goingToWorkOnThis = this.entryToEdit?.running ? true : false; this.shouldRestartEntry = false; - if (this.entryToEdit) { + this.getRecentProjects(); + if (this.entryToEdit && !isEmpty(this.entryToEdit)) { + this.isTechnologiesDisabled = false; this.selectedTechnologies = this.entryToEdit.technologies; const projectFound = this.listProjects.find((project) => project.id === this.entryToEdit.project_id); this.entryForm.setValue({ @@ -142,16 +197,34 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { uri: this.entryToEdit.uri, technology: '', }); + + this.activities$ = this.store.pipe( + select(allActiveActivities), + mergeMap((activeActivities) => + this.store.pipe( + select(allActivities), + map((activities) => + this.findInactiveActivity(activities) !== undefined + ? [...activeActivities, this.findInactiveActivity(activities)] + : activeActivities + ) + ) + ) + ); } else { this.cleanForm(); + this.isTechnologiesDisabled = true; + this.activities$ = this.selectActiveActivities(); } } - cleanForm() { + cleanForm(skipProject: boolean = false): void { this.selectedTechnologies = []; - this.entryForm.setValue({ - project_name: '', - project_id: '', + const projectNameField = this.project_name.value; + const projectName = get(projectNameField, 'search_field', projectNameField); + this.entryForm.reset({ + project_name: skipProject ? projectName : '', + project_id: skipProject ? this.project_id.value : '', activity_id: '', description: '', start_date: formatDate(new Date(), DATE_FORMAT, 'en'), @@ -163,10 +236,28 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { }); } + cleanFieldsForm(): void { + this.cleanForm(false); + } + + selectActiveActivities() { + return this.store.pipe(select(allActiveActivities)); + } + + findInactiveActivity(activities) { + return activities.find( + (activity) => activity.status === 'inactive' && activity.id === this.entryToEdit.activity_id + ); + } + onTechnologiesUpdated($event: string[]) { this.selectedTechnologies = $event; } + getCurrentDate(): string { + return moment(new Date()).format(DATE_FORMAT_YEAR); + } + get project_id() { return this.entryForm.get('project_id'); } @@ -200,18 +291,65 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { this.closeModal?.nativeElement?.click(); } - dateToSubmit(date, hour) { - const entryFormDate = this.entryForm.value[date]; - const updatedHour = this.entryForm.value[hour]; - const updatedDate = new Date(`${entryFormDate}T${updatedHour.trim()}`).toISOString(); + isStartTimeEntryAfterEndedEntry(): boolean { + const startDate = moment(`${this.start_date.value} ${this.start_hour.value}`); + const endDate = moment(`${this.end_date.value} ${this.end_hour.value}`); + return startDate >= endDate; + } + + getDateISOFormat(date: string, hourAndMinutes: string, seconds: number, miliseconds: number): string { + hourAndMinutes = hourAndMinutes.trim(); + const secondsInISOFormat: string = this.getNumberInISOFormat(seconds); + const milisecondsInISOFormat: string = this.getNumberInISOFormat(miliseconds); + const ISOFormat = `${date}T${hourAndMinutes}:${secondsInISOFormat}.${milisecondsInISOFormat}`; + return new Date(ISOFormat).toISOString(); + } + + getNumberInISOFormat(numberToFormat: number): string { + const limitSingleNumber = 9; + const isNumberGreaterThanLimitNumber = numberToFormat > limitSingleNumber; + return isNumberGreaterThanLimitNumber ? numberToFormat.toString() : `0${numberToFormat}`; + } + + dateToSubmit(date: string, hour: string) { + const timeEntryISODate: string = get(this.entryToEdit, date); + let seconds = 0; + let miliseconds = 0; + + if (timeEntryISODate) { + const timeEntryDate: Date = new Date(timeEntryISODate); + seconds = timeEntryDate.getSeconds(); + miliseconds = timeEntryDate.getMilliseconds(); + } + + const newEntryDate: string = this.entryForm.value[date]; + const newEntryHour: string = this.entryForm.value[hour]; + + const updatedDate: string = this.getDateISOFormat(newEntryDate, newEntryHour, seconds, miliseconds); const initialDate = get(this.entryToEdit, date, updatedDate); - const initialHour = formatDate(get(this.entryToEdit, date, updatedDate), 'HH:mm', 'en'); - const dateHasNotChanged = updatedHour === initialHour; + const dateHasNotChanged = initialDate === updatedDate; const result = dateHasNotChanged ? initialDate : updatedDate; return result; } onSubmit() { + + // Debounce submit (Save) button + const button = document.querySelector('#submitButton'); + button.setAttribute('disabled', 'true'); + setTimeout(() => { + button.removeAttribute('disabled'); + }, 3000); + + const emptyValue = ''; + const { project_name, uri, description } = this.entryForm.value; + const areEmptyValues = [uri, description].every(item => item === emptyValue); + const canSkipDescriptionAndURI = PROJECT_NAME_TO_SKIP.some(projectNameItem => project_name.includes(projectNameItem)); + if (project_name.includes(INTERNAL_APP_STRING) && areEmptyValues && !canSkipDescriptionAndURI) { + this.toastrService.warning(EMPTY_FIELDS_ERROR_MESSAGE); + return; + } + if (this.entryForm.invalid) { this.toastrService.warning('Make sure to select a project and activity'); return; @@ -220,6 +358,11 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { const startDateToSubmit = this.dateToSubmit('start_date', 'start_hour'); const endDateToSubmit = this.dateToSubmit('end_date', 'end_hour'); + if (this.isStartTimeEntryAfterEndedEntry() && !this.goingToWorkOnThis) { + this.toastrService.error('You must end the time entry after it started'); + return; + } + const entry = { project_id: this.entryForm.value.project_id, activity_id: this.entryForm.value.activity_id, @@ -230,24 +373,24 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { uri: this.entryForm.value.uri, timezone_offset: new Date().getTimezoneOffset(), }; - if (this.goingToWorkOnThis) { + + if (this.goingToWorkOnThis && this.canMarkEntryAsWIP) { delete entry.end_date; } const isStartDateInTheFuture = moment(startDateToSubmit).isAfter(moment()); const isEndDateInTheFuture = moment(endDateToSubmit).isAfter(moment()); - const timeEntryIsInTheFuture = isStartDateInTheFuture || isEndDateInTheFuture; + const timeEntryIsInTheFuture = isStartDateInTheFuture || (isEndDateInTheFuture && !this.goingToWorkOnThis); if (timeEntryIsInTheFuture) { this.toastrService.error('You cannot start a time-entry in the future'); return; } - this.saveEntry.emit({ entry, shouldRestartEntry: this.shouldRestartEntry }); } - onclickFormAction(isProjectSelected: boolean){ - if (isProjectSelected){ + onclickFormAction(isProjectSelected: boolean) { + if (isProjectSelected) { this.toastrService.warning('Please, first select a project'); } } @@ -257,9 +400,14 @@ export class DetailsFieldsComponent implements OnChanges, OnInit { if (!this.goingToWorkOnThis) { this.entryForm.patchValue({ end_date: formatDate(get(this.entryToEdit, 'start_date', ''), DATE_FORMAT, 'en'), - end_hour: formatDate(get(this.entryToEdit, 'start_date', '00:00'), 'HH:mm', 'en'), + end_hour: formatDate(new Date(), 'HH:mm', 'en'), }); + this.end_date.setValue(this.start_date.value); } this.shouldRestartEntry = !this.entryToEdit?.running && this.goingToWorkOnThis; } + + openOrCloseDatePicker(datepicker: MatDatepicker): void { + return datepicker.opened ? datepicker.close() : datepicker.open(); + } } diff --git a/src/app/modules/shared/components/dropdown/dropdown.component.html b/src/app/modules/shared/components/dropdown/dropdown.component.html new file mode 100644 index 000000000..2eac90228 --- /dev/null +++ b/src/app/modules/shared/components/dropdown/dropdown.component.html @@ -0,0 +1,20 @@ + + \ No newline at end of file diff --git a/src/app/modules/shared/components/dropdown/dropdown.component.scss b/src/app/modules/shared/components/dropdown/dropdown.component.scss new file mode 100644 index 000000000..dbff41db6 --- /dev/null +++ b/src/app/modules/shared/components/dropdown/dropdown.component.scss @@ -0,0 +1,33 @@ +@import '../../../../../styles/colors.scss'; + +.btn-switch { + font-size: 1rem; + border: 1px solid lighten($primary-text, 10%); + border-radius: 20px; + width: clamp(7rem, 50%, 12rem); + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 auto; +} + +.iconTwo { + color: lighten($primary-text, 10%); + text-align: center; +} + +.container-description-status { + display: flex; + align-items: center; + justify-content: flex-start; +} + +.icon { + flex: 0 0 20%; +} + +.description { + flex: 1 1 60%; + padding: 0; + margin: 0; +} diff --git a/src/app/modules/shared/components/dropdown/dropdown.component.spec.ts b/src/app/modules/shared/components/dropdown/dropdown.component.spec.ts new file mode 100644 index 000000000..ca5e237b7 --- /dev/null +++ b/src/app/modules/shared/components/dropdown/dropdown.component.spec.ts @@ -0,0 +1,109 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { DropdownComponent } from './dropdown.component'; + +describe('DropdownComponent', () => { + let component: DropdownComponent; + let fixture: ComponentFixture; + let fakeInfo: any; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ DropdownComponent ] + }) + .compileComponents(); + + fakeInfo = { + id: 'fake', + name: 'fake', + description: 'fake', + tenant_id: '', + status: '', + key: 'active', + _status: false, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-caret-check', + btnName: 'Active', + iconColor: 'text-success' + }; + + fixture = TestBed.createComponent(DropdownComponent); + component = fixture.componentInstance; + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit the item when clicked in method changeOperation', () => { + spyOn(component.updateInfo, 'emit'); + component.info = fakeInfo; + + fixture.detectChanges(); + component.changeOperation(fakeInfo); + + expect(component.updateInfo.emit).toHaveBeenCalled(); + }); + + it('should changed properties of item when is Active', () => { + const fakeInfoActive = { + id: 'fake', + name: 'fake', + description: 'fake', + tenant_id: '', + status: '', + key: 'active', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-caret-check', + btnName: 'Active', + iconColor: 'text-success' + }; + const expectChangeValue = { + btnName: 'Inactive', + btnIcon: 'fa-circle', + iconColor: 'text-danger' + }; + component.info = fakeInfoActive; + + component.changePropertiesItem(fakeInfoActive); + + expect(component.dataChange.btnName).toEqual(expectChangeValue.btnName); + expect(component.dataChange.btnIcon).toEqual(expectChangeValue.btnIcon); + expect(component.dataChange.iconColor).toEqual(expectChangeValue.iconColor); + }); + + it('should change properties of item when is Inactive', () => { + const fakeInfoInactive = { + id: 'fake', + name: 'fake', + description: 'fake', + tenant_id: '', + status: '', + key: 'active', + _status: true, + btnColor: 'btn-white', + btnIcon: 'fa-circle', + btnIconTwo: 'fa-caret-check', + btnName: 'Inactive', + iconColor: 'text-danger' + }; + const expectChangeValue = { + btnName: 'Active', + btnIcon: 'fa-circle', + iconColor: 'text-success' + }; + component.info = fakeInfoInactive; + + component.changePropertiesItem(fakeInfoInactive); + + expect(component.dataChange.btnName).toEqual(expectChangeValue.btnName); + expect(component.dataChange.btnIcon).toEqual(expectChangeValue.btnIcon); + expect(component.dataChange.iconColor).toEqual(expectChangeValue.iconColor); + + }); +}); + + + diff --git a/src/app/modules/shared/components/dropdown/dropdown.component.ts b/src/app/modules/shared/components/dropdown/dropdown.component.ts new file mode 100644 index 000000000..7f3672b26 --- /dev/null +++ b/src/app/modules/shared/components/dropdown/dropdown.component.ts @@ -0,0 +1,44 @@ +import { Output, Component, EventEmitter, Input } from '@angular/core'; + + +@Component({ + selector: 'app-dropdown', + templateUrl: './dropdown.component.html', + styleUrls: ['./dropdown.component.scss'] +}) +export class DropdownComponent { + + @Input() info: any; + @Output() updateInfo: EventEmitter = new EventEmitter(); + + dataChange: any = { + id: '', + name: '', + description: '', + tenant_id: '', + status: '', + key: '', + _status: false, + btnColor: '', + btnIcon: '', + btnIconTwo: '', + btnName: '', + iconColor: '' + }; + + changePropertiesItem({btnName, btnIcon}){ + this.dataChange.btnIcon = btnIcon; + if (btnName === 'Active'){ + this.dataChange.btnName = 'Inactive'; + this.dataChange.iconColor = 'text-danger'; + }else{ + this.dataChange.btnName = 'Active'; + this.dataChange.iconColor = 'text-success'; + } + } + + changeOperation(item: any){ + this.updateInfo.emit(item); + + } +} diff --git a/src/app/modules/shared/components/index.ts b/src/app/modules/shared/components/index.ts index 41d940e42..81c30d5f5 100644 --- a/src/app/modules/shared/components/index.ts +++ b/src/app/modules/shared/components/index.ts @@ -5,3 +5,5 @@ export * from './month-picker/month-picker.component'; export * from './navbar/navbar.component'; export * from './sidebar/sidebar.component'; export * from './user/user.component'; +export * from './dropdown/dropdown.component'; + diff --git a/src/app/modules/shared/components/input-date/input-date.component.html b/src/app/modules/shared/components/input-date/input-date.component.html index 7cc0731c2..80ee3f11d 100644 --- a/src/app/modules/shared/components/input-date/input-date.component.html +++ b/src/app/modules/shared/components/input-date/input-date.component.html @@ -1,10 +1,13 @@ + diff --git a/src/app/modules/shared/components/input-date/input-date.component.spec.ts b/src/app/modules/shared/components/input-date/input-date.component.spec.ts index fe0662d49..f2ccb6753 100644 --- a/src/app/modules/shared/components/input-date/input-date.component.spec.ts +++ b/src/app/modules/shared/components/input-date/input-date.component.spec.ts @@ -1,16 +1,23 @@ -import {waitForAsync, ComponentFixture, fakeAsync, TestBed, tick} from '@angular/core/testing'; -import {InputDateComponent} from './input-date.component'; +import { waitForAsync, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { MatMomentDateModule } from '@angular/material-moment-adapter'; +import { MatInputModule } from '@angular/material/input'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { InputDateComponent } from './input-date.component'; +import * as moment from 'moment'; describe('InputDateComponent', () => { let component: InputDateComponent; let fixture: ComponentFixture; let input; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [InputDateComponent] - }).compileComponents(); - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [InputDateComponent], + imports: [MatInputModule, MatDatepickerModule, MatMomentDateModule], + }).compileComponents(); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(InputDateComponent); @@ -25,11 +32,47 @@ describe('InputDateComponent', () => { }); it('should insert the provided text into the component', fakeAsync(() => { - setInputValue('input', '2020-05-20'); + setInputValue('input', moment('2020-05-20').format('l')); expect(component.value).toEqual('2020-05-20'); })); + it('it calls the close method if opened equals true', () => { + const datepicker: any = { opened : true, open : () => ({}), close : () => ({}) }; + spyOn(datepicker, 'close'); + component.openOrCloseDatePicker(datepicker); + expect(datepicker.close).toHaveBeenCalled(); + }); + + it('it calls the open method if opened equals false', () => { + const datepicker: any = { opened : false, open : () => ({}), close : () => ({}) }; + spyOn(datepicker, 'open'); + component.openOrCloseDatePicker(datepicker); + expect(datepicker.open).toHaveBeenCalled(); + }); + + it('isDisabled should be true if parameter is true', () => { + component.setDisabledState(true); + expect(component.isDisabled).toBe(true); + }); + + it('isDisabled should be false if parameter is false', () => { + component.setDisabledState(false); + expect(component.isDisabled).toBe(false); + }); + + it(`value should be '' in writeValue function when parameter is null`, () => { + const value: any = null; + component.writeValue(value); + expect(component.value).toEqual(''); + }); + + it(`value should be '' in writeValue function when parameter is not defined`, () => { + const value: any = undefined; + component.writeValue(value); + expect(component.value).toEqual(''); + }); + const params: boolean[] = [true, false]; params.forEach(disable => { it('when the disabled attribute is provided, it should disable the input ', fakeAsync(() => { diff --git a/src/app/modules/shared/components/input-date/input-date.component.ts b/src/app/modules/shared/components/input-date/input-date.component.ts index b76072bd8..a05ff44c3 100644 --- a/src/app/modules/shared/components/input-date/input-date.component.ts +++ b/src/app/modules/shared/components/input-date/input-date.component.ts @@ -1,5 +1,8 @@ import {Component, forwardRef} from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; +import { MatDatepicker } from '@angular/material/datepicker'; +import { DATE_FORMAT_YEAR } from 'src/environments/environment'; +import * as moment from 'moment'; @Component({ selector: 'app-input-date', @@ -13,17 +16,15 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; } ] }) + export class InputDateComponent implements ControlValueAccessor { value: string; isDisabled: boolean; onChange = (_: any) => { }; onTouch = () => { }; - constructor() { - } - - onInput(value: string) { - this.value = value; + onInput(value: moment.Moment) { + this.value = value.format(DATE_FORMAT_YEAR); this.onTouch(); this.onChange(this.value); } @@ -47,4 +48,11 @@ export class InputDateComponent implements ControlValueAccessor { setDisabledState(isDisabled: boolean): void { this.isDisabled = isDisabled; } + + openOrCloseDatePicker(datepicker: MatDatepicker): void { + return datepicker.opened ? datepicker.close() : datepicker.open(); + } + getCurrentDate(): string { + return moment(new Date()).format(DATE_FORMAT_YEAR); + } } diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.html b/src/app/modules/shared/components/month-picker/month-picker.component.html index aac899ad4..4229aa45b 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.html +++ b/src/app/modules/shared/components/month-picker/month-picker.component.html @@ -22,8 +22,9 @@
diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.scss b/src/app/modules/shared/components/month-picker/month-picker.component.scss index 29e11f684..432bcfe65 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.scss +++ b/src/app/modules/shared/components/month-picker/month-picker.component.scss @@ -24,6 +24,10 @@ } } +.selected-month { + color: white; +} + .align-item { display: flex; justify-content: center; diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts b/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts index b55811102..0cdc3d5dc 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts +++ b/src/app/modules/shared/components/month-picker/month-picker.component.spec.ts @@ -1,13 +1,14 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import * as moment from 'moment'; -import { NEVER } from 'rxjs'; +import { CookieService } from 'ngx-cookie-service'; +import { FeatureToggle } from 'src/environments/enum'; import { MonthPickerComponent } from './month-picker.component'; describe('MonthPickerComponent', () => { let component: MonthPickerComponent; let fixture: ComponentFixture; - + let cookieService: CookieService; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ MonthPickerComponent ] @@ -17,6 +18,7 @@ describe('MonthPickerComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(MonthPickerComponent); + cookieService = TestBed.inject(CookieService); component = fixture.componentInstance; fixture.detectChanges(); }); @@ -69,4 +71,208 @@ describe('MonthPickerComponent', () => { expect(component.selectDate).toHaveBeenCalledWith(monthSelect, yearSelect); }); + it('monthEnable sets disabled to true on futures months', () => { + const monthFuture = component.monthCurrent + 1; + expect(component.monthEnable(monthFuture)).toBeTrue(); + }); + + it('call cookieService.get() when component was create', () => { + spyOn(cookieService, 'get'); + + component.ngOnInit(); + + expect(cookieService.get).toHaveBeenCalledWith(FeatureToggle.TIME_TRACKER_CALENDAR); + }); + + it('set true in isFeatureToggleCalendarActive when component was create', () => { + const expectCookieValue = true; + spyOn(cookieService, 'get').and.returnValue(`${ expectCookieValue }`); + + component.ngOnInit(); + + expect(component.isFeatureToggleCalendarActive).toEqual(expectCookieValue); + }); + + it('set false in isFeatureToggleCalendarActive when component was create', () => { + const expectCookieValue = false; + spyOn(cookieService, 'get').and.returnValue(`${ expectCookieValue }`); + + component.ngOnInit(); + + expect(component.isFeatureToggleCalendarActive).toEqual(expectCookieValue); + }); + + it('set false in isFeatureToggleCalendarActive when cookie does not exist', () => { + const expectCookieValue = false; + spyOn(cookieService, 'get'); + + component.ngOnInit(); + + expect(component.isFeatureToggleCalendarActive).toEqual(expectCookieValue); + }); + + it('call refresData when updating the value of selectedDate', () => { + const fakeSelectedDate: moment.Moment = moment(new Date()); + spyOn(component, 'refreshDate'); + + component.selectedDate = fakeSelectedDate; + + expect(component.refreshDate).toHaveBeenCalledWith(fakeSelectedDate); + }); + + it('not set value of selectedDate in selectedDateMoment when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-05')); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedDateMoment).not.toEqual(fakeSelectedDate); + }); + + it('set value of selectedDate in selectedDateMoment when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-05')); + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedDateMoment).toEqual(fakeSelectedDate); + }); + + + it('set current Month index in selectedMonthIndex when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-05')); + const currentDate: moment.Moment = moment(new Date()); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedMonthIndex).toEqual(currentDate.month()); + }); + + it('set month of selectedDate in selectedMonthIndex when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-05')); + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedMonthIndex).toEqual(fakeSelectedDate.month()); + }); + + it('set current year as a text in selectedYearText when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2020-07-05')); + const currentDate: number = moment(new Date()).year(); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedYearText).toEqual(`${currentDate}`); + }); + + it('set year as a text of selectedDate in selectedYearText when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('1999-07-05')); + const expectedYear = '1999'; + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedYearText).toEqual(expectedYear); + }); + + it('set current year in selectedYear when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2020-07-05')); + const currentDate: number = moment(new Date()).year(); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedYear).toEqual(currentDate); + }); + + it('set year as a text of selectedDate in selectedYear when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('1999-07-05')); + const expectedYear = 1999; + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.selectedYear).toEqual(expectedYear); + }); + + it('not true in showArrowNext when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('1999-07-05')); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.showArrowNext).not.toEqual(true); + }); + + it('false in showArrowNext when isFeatureToggleCalendarActive is false', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('1999-07-05')); + component.isFeatureToggleCalendarActive = false; + + component.refreshDate(fakeSelectedDate); + + expect(component.showArrowNext).toEqual(false); + }); + + it('set true in showArrowNext when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('1999-07-05')); + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.showArrowNext).toEqual(true); + }); + + it('set false in showArrowNext when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date()).add(1, 'month'); + component.isFeatureToggleCalendarActive = true; + + component.refreshDate(fakeSelectedDate); + + expect(component.showArrowNext).toEqual(false); + }); + + it('isSelectedMonth returns true when isFeatureToggleCalendarActive is true', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-06')); + const expectedReturn = true; + const fakeFeatureToggleValue = true; + component.selectedMonthIndex = fakeSelectedDate.month(); + component.selectedYear = fakeSelectedDate.year(); + component.selectedDateMoment = fakeSelectedDate; + component.isFeatureToggleCalendarActive = fakeFeatureToggleValue; + + const response = component.isSelectedMonth(fakeSelectedDate.month()); + + expect(response).toEqual(expectedReturn); + }); + + it('isSelectedMonth returns false when isFeatureToggleCalendarActive is true and the months are not the same', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-06')); + const expectedReturn = false; + const fakeFeatureToggleValue = true; + component.selectedMonthIndex = fakeSelectedDate.month(); + component.selectedYear = fakeSelectedDate.year(); + component.selectedDateMoment = fakeSelectedDate; + component.isFeatureToggleCalendarActive = fakeFeatureToggleValue; + + const response = component.isSelectedMonth(fakeSelectedDate.add(1, 'month').month()); + + expect(response).toEqual(expectedReturn); + }); + + it('isSelectedMonth returns false when isFeatureToggleCalendarActive is true and the years are not the same', () => { + const fakeSelectedDate: moment.Moment = moment(new Date('2021-07-06')); + const expectedReturn = false; + const fakeFeatureToggleValue = true; + component.selectedMonthIndex = fakeSelectedDate.month(); + component.selectedYear = fakeSelectedDate.year(); + component.selectedDateMoment = fakeSelectedDate.add(1, 'year'); + component.isFeatureToggleCalendarActive = fakeFeatureToggleValue; + + const response = component.isSelectedMonth(fakeSelectedDate.month()); + + expect(response).toEqual(expectedReturn); + }); }); diff --git a/src/app/modules/shared/components/month-picker/month-picker.component.ts b/src/app/modules/shared/components/month-picker/month-picker.component.ts index fc457f33f..282d7a0e5 100644 --- a/src/app/modules/shared/components/month-picker/month-picker.component.ts +++ b/src/app/modules/shared/components/month-picker/month-picker.component.ts @@ -1,5 +1,7 @@ -import { Component, OnInit, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core'; import * as moment from 'moment'; +import { CookieService } from 'ngx-cookie-service'; +import { FeatureToggle } from 'src/environments/enum'; @Component({ selector: 'app-month-picker', @@ -7,6 +9,10 @@ import * as moment from 'moment'; styleUrls: ['./month-picker.component.scss'] }) export class MonthPickerComponent implements OnInit { + @Input() + set selectedDate(selectedDateMoment: moment.Moment){ + this.refreshDate(selectedDateMoment); + } @Output() dateSelected = new EventEmitter<{ monthIndex: number; year: number; @@ -20,8 +26,10 @@ export class MonthPickerComponent implements OnInit { months: Array = []; currentYear = new Date().getFullYear(); showArrowNext = false; - - constructor() { + monthCurrent = moment().month(); + selectedDateMoment: moment.Moment = moment(); + isFeatureToggleCalendarActive: boolean; + constructor(private cookiesService: CookieService) { this.selectedYearMoment = moment(); this.selectedMonthMoment = moment(); this.months = moment.months(); @@ -31,9 +39,20 @@ export class MonthPickerComponent implements OnInit { } ngOnInit() { + this.isFeatureToggleCalendarActive = (this.cookiesService.get(FeatureToggle.TIME_TRACKER_CALENDAR) === 'true'); this.selectDate(this.selectedMonthIndex, this.selectedYear); } + refreshDate(newDate: moment.Moment){ + if (this.isFeatureToggleCalendarActive){ + this.selectedDateMoment = newDate; + this.selectedMonthIndex = this.selectedDateMoment.month(); + this.selectedYearText = moment(this.selectedDateMoment).format('YYYY'); + this.selectedYear = this.selectedDateMoment.year(); + this.showArrowNext = this.selectedYear < this.currentYear; + } + } + changeYear(changeAction: string) { this.selectedYearMoment[changeAction](1, 'year'); this.selectedYearText = moment(this.selectedYearMoment).format('YYYY'); @@ -49,8 +68,19 @@ export class MonthPickerComponent implements OnInit { this.selectedMonthIndex = monthIndex; this.selectDate(this.selectedMonthIndex, this.selectedYear); } - + monthEnable(monthIndex: number){ + return( + this.monthCurrent < monthIndex && + this.selectedYear === this.currentYear + ); + } isSelectedMonth(monthIndex: number) { + if (this.isFeatureToggleCalendarActive) { + return ( + this.selectedMonthIndex === monthIndex && + this.selectedYear === this.selectedDateMoment.year() + ); + } return ( this.selectedMonthIndex === monthIndex && this.selectedYear === this.selectedYearMoment.year() diff --git a/src/app/modules/shared/components/search-activity/search-activity.component.html b/src/app/modules/shared/components/search-activity/search-activity.component.html new file mode 100644 index 000000000..f1960b694 --- /dev/null +++ b/src/app/modules/shared/components/search-activity/search-activity.component.html @@ -0,0 +1,7 @@ +
+ + + {{activity.name}} + + +
\ No newline at end of file diff --git a/src/app/modules/shared/components/search-activity/search-activity.component.scss b/src/app/modules/shared/components/search-activity/search-activity.component.scss new file mode 100644 index 000000000..d21fef41b --- /dev/null +++ b/src/app/modules/shared/components/search-activity/search-activity.component.scss @@ -0,0 +1,9 @@ +label { + width: 225px; +} +.selectActivity { + display: inline-block; + width: 300px; + padding: 0 12px 15px 12px; +} + diff --git a/src/app/modules/shared/components/search-activity/search-activity.component.spec.ts b/src/app/modules/shared/components/search-activity/search-activity.component.spec.ts new file mode 100644 index 000000000..f1f8a9cb6 --- /dev/null +++ b/src/app/modules/shared/components/search-activity/search-activity.component.spec.ts @@ -0,0 +1,34 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; + +import { SearchActivityComponent } from './search-activity.component'; +import { NgSelectModule } from '@ng-select/ng-select'; + +describe('SearchActivityComponent', () => { + let component: SearchActivityComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ FormsModule, NgSelectModule ], + declarations: [ SearchActivityComponent ], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchActivityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changedFilterValue event #changeFilterValue', () => { + component.selectedActivity = 'angular'; + spyOn(component.selectedActivityId, 'emit'); + component.updateActivity(); + expect(component.selectedActivityId.emit).toHaveBeenCalled(); + }); +}); diff --git a/src/app/modules/shared/components/search-activity/search-activity.component.ts b/src/app/modules/shared/components/search-activity/search-activity.component.ts new file mode 100644 index 000000000..6ab8b55a7 --- /dev/null +++ b/src/app/modules/shared/components/search-activity/search-activity.component.ts @@ -0,0 +1,22 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-search-activity', + templateUrl: './search-activity.component.html', + styleUrls: ['./search-activity.component.scss'], +}) + +export class SearchActivityComponent { + + readonly ALLOW_SELECT_MULTIPLE = false; + selectedActivity: string; + + @Input() activities: string[] = []; + + @Output() selectedActivityId = new EventEmitter(); + + updateActivity() { + this.selectedActivityId.emit(this.selectedActivity || '*' ); + } +} + diff --git a/src/app/modules/shared/components/search-project/search-project.component.html b/src/app/modules/shared/components/search-project/search-project.component.html new file mode 100644 index 000000000..c97e53194 --- /dev/null +++ b/src/app/modules/shared/components/search-project/search-project.component.html @@ -0,0 +1,7 @@ +
+ + + {{project.customer.name}} - {{project.name}} + + +
\ No newline at end of file diff --git a/src/app/modules/shared/components/search-project/search-project.component.scss b/src/app/modules/shared/components/search-project/search-project.component.scss new file mode 100644 index 000000000..786299355 --- /dev/null +++ b/src/app/modules/shared/components/search-project/search-project.component.scss @@ -0,0 +1,9 @@ +label { + width: 225px; +} +.selectProject { + display: inline-block; + width: 600px; + padding: 0 12px 15px 12px; +} + diff --git a/src/app/modules/shared/components/search-project/search-project.component.spec.ts b/src/app/modules/shared/components/search-project/search-project.component.spec.ts new file mode 100644 index 000000000..276c39049 --- /dev/null +++ b/src/app/modules/shared/components/search-project/search-project.component.spec.ts @@ -0,0 +1,34 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; + +import { SearchProjectComponent } from './search-project.component'; +import { NgSelectModule } from '@ng-select/ng-select'; + +describe('SearchActivityComponent', () => { + let component: SearchProjectComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ FormsModule, NgSelectModule ], + declarations: [ SearchProjectComponent ], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchProjectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should emit changedFilterValue event #changeFilterValue', () => { + component.selectedProject = 'angular'; + spyOn(component.selectedProjectId, 'emit'); + component.updateProject(); + expect(component.selectedProjectId.emit).toHaveBeenCalled(); + }); +}); diff --git a/src/app/modules/shared/components/search-project/search-project.component.ts b/src/app/modules/shared/components/search-project/search-project.component.ts new file mode 100644 index 000000000..97e04197e --- /dev/null +++ b/src/app/modules/shared/components/search-project/search-project.component.ts @@ -0,0 +1,19 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-search-project', + templateUrl: './search-project.component.html', + styleUrls: ['./search-project.component.scss'], +}) +export class SearchProjectComponent { + readonly ALLOW_SELECT_MULTIPLE = false; + selectedProject: string; + + @Input() projects: string[] = []; + + @Output() selectedProjectId = new EventEmitter(); + + updateProject() { + this.selectedProjectId.emit(this.selectedProject || '*'); + } +} diff --git a/src/app/modules/shared/components/search-user/search-user.component.html b/src/app/modules/shared/components/search-user/search-user.component.html new file mode 100644 index 000000000..2b9c7916d --- /dev/null +++ b/src/app/modules/shared/components/search-user/search-user.component.html @@ -0,0 +1,7 @@ +
+ + + 👤{{user.name}}📨{{ user.email}} + + +
\ No newline at end of file diff --git a/src/app/modules/shared/components/search-user/search-user.component.scss b/src/app/modules/shared/components/search-user/search-user.component.scss new file mode 100644 index 000000000..d36c9b4d5 --- /dev/null +++ b/src/app/modules/shared/components/search-user/search-user.component.scss @@ -0,0 +1,9 @@ +label { + width: 225px; +} +.selectUser { + display: inline-block; + width: 500px; + padding: 0 12px 15px 12px; +} + diff --git a/src/app/modules/shared/components/search-user/search-user.component.ts b/src/app/modules/shared/components/search-user/search-user.component.ts new file mode 100644 index 000000000..e2a95db94 --- /dev/null +++ b/src/app/modules/shared/components/search-user/search-user.component.ts @@ -0,0 +1,22 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-search-user', + templateUrl: './search-user.component.html', + styleUrls: ['./search-user.component.scss'], +}) + +export class SearchUserComponent { + + readonly ALLOW_SELECT_MULTIPLE = true; + selectedUser: string[]; + + @Input() users: string[] = []; + + @Output() selectedUserId = new EventEmitter(); + + updateUser() { + this.selectedUserId.emit(this.selectedUser.length === 0 ? '*' : this.selectedUser); + } +} + diff --git a/src/app/modules/shared/components/search/search.component.spec.ts b/src/app/modules/shared/components/search/search.component.spec.ts index 8c1298424..da270c9d4 100644 --- a/src/app/modules/shared/components/search/search.component.spec.ts +++ b/src/app/modules/shared/components/search/search.component.spec.ts @@ -1,4 +1,5 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { SearchComponent } from './search.component'; @@ -8,7 +9,8 @@ describe('SearchComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - declarations: [SearchComponent], + imports: [ FormsModule ], + declarations: [ SearchComponent ], }).compileComponents(); })); diff --git a/src/app/modules/shared/components/sidebar/sidebar.component.html b/src/app/modules/shared/components/sidebar/sidebar.component.html index b18c05e2d..66d7881b5 100644 --- a/src/app/modules/shared/components/sidebar/sidebar.component.html +++ b/src/app/modules/shared/components/sidebar/sidebar.component.html @@ -1,38 +1,49 @@ -
- @@ -56,6 +56,7 @@ maxlength="1500" (blur)="onSubmit()" formControlName="description" + placeholder="Write a brief description of your activity" class="form-control" id="NotesTextarea" rows="2"> diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss index dc47b027b..c6532e8d9 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.scss @@ -9,3 +9,14 @@ .hidden { display: none; } + +.url-ticket-input { + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +::ng-deep .ngx-timepicker { + border: 1px solid #ced4da; + border-radius: 0.25rem; + padding: 2px; +} diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts index aa588c670..ecccab02c 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.spec.ts @@ -1,20 +1,23 @@ import { Subscription } from 'rxjs'; -import { LoadActiveEntry, EntryActionTypes, UpdateEntry } from './../../store/entry.actions'; +import { LoadActiveEntry, EntryActionTypes } from './../../store/entry.actions'; import { ActivityManagementActionTypes } from './../../../activities-management/store/activity-management.actions'; -import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing'; -import {MockStore, provideMockStore} from '@ngrx/store/testing'; +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { FormsModule, ReactiveFormsModule, FormBuilder } from '@angular/forms'; -import {TechnologyState} from '../../../shared/store/technology.reducers'; -import {allTechnologies} from '../../../shared/store/technology.selectors'; -import {EntryFieldsComponent} from './entry-fields.component'; -import {ProjectState} from '../../../customer-management/components/projects/components/store/project.reducer'; -import {getCustomerProjects} from '../../../customer-management/components/projects/components/store/project.selectors'; +import { TechnologyState } from '../../../shared/store/technology.reducers'; +import { allTechnologies } from '../../../shared/store/technology.selectors'; +import { EntryFieldsComponent } from './entry-fields.component'; +import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; +import { getCustomerProjects } from '../../../customer-management/components/projects/components/store/project.selectors'; import { ActionsSubject } from '@ngrx/store'; import { IndividualConfig, ToastrService } from 'ngx-toastr'; import { formatDate } from '@angular/common'; import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; import * as moment from 'moment'; import { DATE_FORMAT_YEAR } from 'src/environments/environment'; +import { TechnologiesComponent } from '../../../shared/components/technologies/technologies.component'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { EMPTY_FIELDS_ERROR_MESSAGE } from 'src/app/modules/shared/messages'; describe('EntryFieldsComponent', () => { type Merged = TechnologyState & ProjectState; @@ -26,8 +29,8 @@ describe('EntryFieldsComponent', () => { let entryForm; const actionSub: ActionsSubject = new ActionsSubject(); const toastrServiceStub = { - error: (message?: string, title?: string, override?: Partial) => { }, - warning: (message?: string, title?: string, override?: Partial) => { } + error: (message?: string, title?: string, override?: Partial) => {}, + warning: (message?: string, title?: string, override?: Partial) => {}, }; const mockDate = '2020-12-01T12:00:00'; const lastDate = moment(mockDate).format(DATE_FORMAT_YEAR); @@ -38,18 +41,19 @@ describe('EntryFieldsComponent', () => { const state = { projects: { - projects: [{id: 'id', name: 'name', project_type_id: ''}], - customerProjects: [{id: 'id', name: 'name', description: 'description', project_type_id: '123'}], + projects: [{ id: 'id', name: 'name', project_type_id: '' }], + customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, }, technologies: { - technologyList: {items: [{name: 'java'}]}, + technologyList: { items: [{ name: 'java' }] }, isLoading: false, }, activities: { - data: [{id: 'xyz', tenant_id: 'ioet', deleted: null, name: 'Training 2'}], + data: [{ id: 'xyz', tenant_id: 'ioet', deleted: null, name: 'Training 2', status: 'active' }], isLoading: false, message: 'Data fetch successfully!', activityIdToEdit: '', @@ -65,28 +69,34 @@ describe('EntryFieldsComponent', () => { }, entryList: [], message: '', - timeEntriesDataSource: { data: [ - { - activity_id: 'xyz', - activity_name: 'abc', - id: 'id-15', - project_id: 'project-id-15', - description: 'description for an entry', - uri: 'abc', - start_date : moment().toISOString(), - end_date : moment().toISOString(), - }, - { - activity_id: 'xyz', - activity_name: 'abc', - id: 'id-15', - project_id: 'project-id-15', - description: 'description for an entry', - uri: 'abc', - start_date : lastStartHourEntryEntered, - end_date : lastEndHourEntryEntered, - } - ]} + timeEntriesDataSource: { + data: [ + { + activity_id: 'xyz', + activity_name: 'abc', + id: 'id-15', + project_id: 'project-id-15', + description: 'description for an entry', + uri: 'abc', + start_date: moment().toISOString(), + end_date: moment().toISOString(), + project_name: 'project_name', + customer_name: 'customer_name', + }, + { + activity_id: 'xyz', + activity_name: 'abc', + id: 'id-15', + project_id: 'project-id-15', + description: 'description for an entry', + uri: 'abc', + start_date: lastStartHourEntryEntered, + end_date: lastEndHourEntryEntered, + project_name: 'project_name', + customer_name: 'customer name', + }, + ], + }, }, }; @@ -96,25 +106,32 @@ describe('EntryFieldsComponent', () => { project_id: 'project-id-15', description: 'description for active entry', uri: 'abc', - start_date : moment(mockDate).format(DATE_FORMAT_YEAR), - start_hour : moment(mockDate).format('HH:mm'), + start_date: moment(mockDate).format(DATE_FORMAT_YEAR), + start_hour: moment(mockDate).format('HH:mm'), + customer_name: 'ioet', + }; + + const mockEntryOverlap = { + update_last_entry_if_overlap: true, }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [EntryFieldsComponent], - providers: [ - provideMockStore({initialState: state}), - { provide: ActionsSubject, useValue: actionSub }, - { provide: ToastrService, useValue: toastrServiceStub } - ], - imports: [FormsModule, ReactiveFormsModule, NgxMaterialTimepickerModule], - }).compileComponents(); - store = TestBed.inject(MockStore); - entryForm = TestBed.inject(FormBuilder); - mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies); - mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); - })); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [EntryFieldsComponent, TechnologiesComponent], + providers: [ + provideMockStore({ initialState: state }), + { provide: ActionsSubject, useValue: actionSub }, + { provide: ToastrService, useValue: toastrServiceStub }, + ], + imports: [FormsModule, ReactiveFormsModule, NgxMaterialTimepickerModule, NgSelectModule], + }).compileComponents(); + store = TestBed.inject(MockStore); + entryForm = TestBed.inject(FormBuilder); + mockTechnologySelector = store.overrideSelector(allTechnologies, state.technologies); + mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(EntryFieldsComponent); @@ -137,31 +154,34 @@ describe('EntryFieldsComponent', () => { spyOn(component.entryForm, 'patchValue'); component.setDataToUpdate(entry); expect(component.entryForm.patchValue).toHaveBeenCalledTimes(1); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( - { - description: entryDataForm.description, - uri: entryDataForm.uri, - activity_id: entryDataForm.activity_id, - start_hour: formatDate(entry.start_date, 'HH:mm', 'en'), - start_date : moment(mockDate).format(DATE_FORMAT_YEAR), - } - ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ + description: entryDataForm.description, + uri: entryDataForm.uri, + activity_id: entryDataForm.activity_id, + start_hour: formatDate(entry.start_date, 'HH:mm', 'en'), + start_date: moment(mockDate).format(DATE_FORMAT_YEAR), + }); expect(component.selectedTechnologies).toEqual([]); }); it('displays error message when the date selected is in the future', () => { - const mockEntry = { ...entry, - start_date : moment().format(DATE_FORMAT_YEAR), - start_hour : moment().format('HH:mm') + const startMoment = moment(); + if (startMoment.format('HH') === '23') { + return; // form logic does not handle day overlap + } + const mockEntry = { + ...entry, + start_date: startMoment.format(DATE_FORMAT_YEAR), + start_hour: startMoment.format('HH:mm'), }; component.newData = mockEntry; - component.activeEntry = mockEntry ; + component.activeEntry = mockEntry; component.setDataToUpdate(mockEntry); spyOn(toastrServiceStub, 'error'); - const hourInTheFuture = moment().add(1, 'hours').format('HH:mm'); - component.entryForm.patchValue({ start_hour : hourInTheFuture}); + const hourInTheFuture = startMoment.add(1, 'hours').format('HH:mm'); + component.entryForm.patchValue({ start_hour: hourInTheFuture }); component.onUpdateStartHour(); expect(toastrServiceStub.error).toHaveBeenCalled(); @@ -170,12 +190,12 @@ describe('EntryFieldsComponent', () => { it('Displays an error message when the active entry has start_time before the start_time of another entry', () => { component.newData = entry; - component.activeEntry = entry ; + component.activeEntry = entry; component.setDataToUpdate(entry); spyOn(toastrServiceStub, 'error'); const hourInThePast = moment(mockDate).subtract(6, 'hour').format('HH:mm'); - component.entryForm.patchValue({ start_hour : hourInThePast}); + component.entryForm.patchValue({ start_hour: hourInThePast }); component.onUpdateStartHour(); expect(toastrServiceStub.error).toHaveBeenCalled(); @@ -190,92 +210,113 @@ describe('EntryFieldsComponent', () => { it('should reset displayed time and hide control buttons when cancelTimeInUpdate', () => { component.newData = entry; - component.activeEntry = entry ; + component.activeEntry = entry; component.setDataToUpdate(entry); const updatedTime = moment(mockDate).format('HH:mm'); - component.entryForm.patchValue({ start_hour : updatedTime}); + component.entryForm.patchValue({ start_hour: updatedTime }); spyOn(component.entryForm, 'patchValue'); component.cancelTimeInUpdate(); expect(component.showTimeInbuttons).toEqual(false); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( - { - start_hour: component.newData.start_hour - } - ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ + start_hour: component.newData.start_hour, + }); }); it('should reset to current start_date when start_date has an error', () => { component.newData = entry; - component.activeEntry = entry ; + component.activeEntry = entry; component.setDataToUpdate(entry); const updatedTime = moment(mockDate).subtract(6, 'hours').format('HH:mm'); - component.entryForm.patchValue({ start_hour : updatedTime}); + component.entryForm.patchValue({ start_hour: updatedTime }); spyOn(component.entryForm, 'patchValue'); component.onUpdateStartHour(); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( - { - start_hour: component.newData.start_hour - } - ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ + start_hour: component.newData.start_hour, + }); expect(component.showTimeInbuttons).toEqual(false); }); it('If start hour is in the future, reset to initial start_date in form', () => { - const mockEntry = { ...entry, - start_date : moment().format(DATE_FORMAT_YEAR), - start_hour : moment().format('HH:mm') + const startMoment = moment(); + if (startMoment.format('HH') === '23') { + return; // form logic does not handle day overlap + } + const mockEntry = { + ...entry, + start_date: startMoment.format(DATE_FORMAT_YEAR), + start_hour: startMoment.format('HH:mm'), }; component.newData = mockEntry; component.activeEntry = mockEntry; component.setDataToUpdate(mockEntry); - const hourInTheFuture = moment().add(1, 'hours').format('HH:mm'); - component.entryForm.patchValue({ start_hour : hourInTheFuture}); + const hourInTheFuture = startMoment.add(1, 'hours').format('HH:mm'); + component.entryForm.patchValue({ start_hour: hourInTheFuture }); spyOn(component.entryForm, 'patchValue'); component.onUpdateStartHour(); - expect(component.entryForm.patchValue).toHaveBeenCalledWith( - { - start_hour: component.newData.start_hour - } - ); + expect(component.entryForm.patchValue).toHaveBeenCalledWith({ + start_hour: component.newData.start_hour, + }); expect(component.showTimeInbuttons).toEqual(false); }); it('when a start hour is updated, then dispatch UpdateActiveEntry', () => { + component.newData = mockEntryOverlap; component.activeEntry = entry; component.setDataToUpdate(entry); const updatedTime = moment(mockDate).format('HH:mm'); - component.entryForm.patchValue({ start_hour : updatedTime}); + component.entryForm.patchValue({ start_hour: updatedTime }); spyOn(store, 'dispatch'); component.onUpdateStartHour(); + expect(store.dispatch).toHaveBeenCalled(); expect(component.showTimeInbuttons).toEqual(false); }); - it('When start_time is updated, component.last_entry is equal to time entry in the position 1', waitForAsync(() => { + it('when a start hour is updated, but the entry is invalid, then do not dispatch UpdateActiveEntry', () => { + component.newData = mockEntryOverlap; component.activeEntry = entry; component.setDataToUpdate(entry); const updatedTime = moment(mockDate).format('HH:mm'); - component.entryForm.patchValue({ start_hour : updatedTime}); + component.entryForm.patchValue({ start_hour: updatedTime }); + spyOn(store, 'dispatch'); + spyOn(component, 'entryFormIsValidate').and.returnValue(false); + component.onUpdateStartHour(); - expect(component.lastEntry).toBe(state.entries.timeEntriesDataSource.data[1]); - })); + expect(store.dispatch).not.toHaveBeenCalled(); + }); + + it( + 'When start_time is updated, component.last_entry is equal to time entry in the position 1', + waitForAsync(() => { + component.newData = mockEntryOverlap; + component.activeEntry = entry; + component.setDataToUpdate(entry); + const updatedTime = moment(mockDate).format('HH:mm'); + + component.entryForm.patchValue({ start_hour: updatedTime }); + component.onUpdateStartHour(); + + expect(component.lastEntry).toBe(state.entries.timeEntriesDataSource.data[1]); + }) + ); - it('When start_time is updated for a time entry. UpdateCurrentOrLastEntry action is dispatched', () => { + it('When start_time is updated for a valid time entry. UpdateCurrentOrLastEntry action is dispatched', () => { + component.newData = mockEntryOverlap; component.activeEntry = entry; component.setDataToUpdate(entry); const updatedTime = moment(mockDate).subtract(4, 'hours').format('HH:mm'); - component.entryForm.patchValue({ start_hour : updatedTime}); + component.entryForm.patchValue({ start_hour: updatedTime }); spyOn(store, 'dispatch'); component.onUpdateStartHour(); @@ -283,34 +324,55 @@ describe('EntryFieldsComponent', () => { expect(store.dispatch).toHaveBeenCalledTimes(1); }); - it('when a technology is added, then dispatch UpdateActiveEntry', () => { + it('when a technology is added or removed, then dispatch UpdateActiveEntry', () => { const addedTechnologies = ['react']; + const isEntryFormValid = spyOn(component, 'entryFormIsValidate').and.returnValue(true); spyOn(store, 'dispatch'); - component.onTechnologyAdded(addedTechnologies); - expect(store.dispatch).toHaveBeenCalled(); + component.onTechnologyUpdated(addedTechnologies); + expect(isEntryFormValid).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalled(); }); - it('when a technology is removed, then dispatch UpdateActiveEntry', () => { - const addedTechnologies = ['react']; + it('entryFormIsValidate returns false when data in the form is not valid', () => { + component.newData = mockEntryOverlap; - spyOn(store, 'dispatch'); + const invalidEntry = {...entry, activity_id: ''}; + component.activeEntry = invalidEntry; + component.setDataToUpdate(invalidEntry); - component.onTechnologyAdded(addedTechnologies); - expect(store.dispatch).toHaveBeenCalled(); + spyOn(component, 'requiredFieldsForInternalAppExist').and.returnValue(true); + + const result = component.entryFormIsValidate(); + + expect(result).toBe(false); + }); + + it('entryFormIsValidate returns false if not all required fields are present despite data in the form being valid', () => { + component.newData = mockEntryOverlap; + component.activeEntry = entry; + component.setDataToUpdate(entry); + spyOn(component, 'requiredFieldsForInternalAppExist').and.returnValue(false); + const result = component.entryFormIsValidate(); + + expect(result).toBe(false); }); - it('uses the form to check if is valid or not', () => { - entryForm.valid = false; + it('entryFormIsValidate returns true when required fields are present and data in the form is valid', () => { + component.newData = mockEntryOverlap; + component.activeEntry = entry; + component.setDataToUpdate(entry); + spyOn(component, 'requiredFieldsForInternalAppExist').and.returnValue(true); const result = component.entryFormIsValidate(); - expect(result).toBe(entryForm.valid); + expect(result).toBe(true); }); it('dispatches an action when onSubmit is called', () => { + spyOn(component, 'entryFormIsValidate').and.returnValue(true); spyOn(store, 'dispatch'); component.onSubmit(); @@ -319,23 +381,22 @@ describe('EntryFieldsComponent', () => { }); it('dispatches an action when onTechnologyRemoved is called', () => { + spyOn(component, 'entryFormIsValidate').and.returnValue(true); spyOn(store, 'dispatch'); - component.onTechnologyRemoved(['foo']); + component.onTechnologyUpdated(['foo']); expect(store.dispatch).toHaveBeenCalled(); }); - it('sets the technologies on the class when entry has technologies', () => { - const entryData = { ...entry, technologies: ['foo']}; + const entryData = { ...entry, technologies: ['foo'] }; component.setDataToUpdate(entryData); expect(component.selectedTechnologies).toEqual(entryData.technologies); }); - it('activites are populated using the payload of the action', () => { const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; const action = { @@ -348,7 +409,55 @@ describe('EntryFieldsComponent', () => { expect(component.activities).toEqual(action.payload); }); - it('LoadActiveEntry is dispatchen after LOAD_ACTIVITIES_SUCCESS', () => { + it('activites are ordered using the payload of the action', () => { + const activities = [ + { + id: '004', + name: 'Meeting', + description: 'Some description', + }, + { + id: '005', + name: 'ABCD', + description: 'Some description', + }, + { + id: '006', + name: 'XYZA', + description: 'Some description', + }, + ]; + + const activitiesOrdered = [ + { + id: '005', + name: 'ABCD', + description: 'Some description', + }, + { + id: '004', + name: 'Meeting', + description: 'Some description', + }, + { + id: '006', + name: 'XYZA', + description: 'Some description', + }, + ]; + + const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; + const action = { + type: ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS, + payload: activities, + }; + + actionSubject.next(action); + + expect(component.activities).toEqual(activitiesOrdered); + }); + + it('LoadActiveEntry is dispatched after LOAD_ACTIVITIES_SUCCESS', () => { spyOn(store, 'dispatch'); const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; @@ -368,7 +477,7 @@ describe('EntryFieldsComponent', () => { const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; const action = { type: EntryActionTypes.CREATE_ENTRY_SUCCESS, - payload: {end_date: null}, + payload: { end_date: null }, }; actionSubject.next(action); @@ -382,7 +491,7 @@ describe('EntryFieldsComponent', () => { const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; const action = { type: EntryActionTypes.CREATE_ENTRY_SUCCESS, - payload: {end_date: new Date()}, + payload: { end_date: new Date() }, }; actionSubject.next(action); @@ -425,4 +534,87 @@ describe('EntryFieldsComponent', () => { expect(component.loadActiveEntrySubscription.unsubscribe).toHaveBeenCalled(); expect(component.actionSetDateSubscription.unsubscribe).toHaveBeenCalled(); }); + + // we need to fix this test. Until then, we skip it + xit('when a activity is not register in DB should show activatefocus in select activity', () => { + const activitiesMock = [ + { + id: 'xyz', + name: 'test', + description: 'test1', + }, + ]; + const data = { + activity_id: 'xyz', + description: '', + start_date: moment().format(DATE_FORMAT_YEAR), + start_hour: moment().format('HH:mm'), + uri: '', + }; + component.activities = activitiesMock; + component.entryForm.patchValue({ + description: data.description, + uri: data.uri, + activity_id: data.activity_id, + start_date: data.start_date, + start_hour: data.start_hour, + }); + component.ngOnInit(); + component.activateFocus(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const autofocus = fixture.nativeElement.querySelector('select'); + expect(autofocus).toHaveBeenCalled(); + }); + }); + + it('should show an error message if description and ticket fields are empty for internal apps', () => { + spyOn(toastrServiceStub, 'error'); + const result = component.requiredFieldsForInternalAppExist('ioet', 'project name'); + expect(toastrServiceStub.error).toHaveBeenCalledWith(EMPTY_FIELDS_ERROR_MESSAGE); + expect(result).toBe(false); + }); + + it('should return true if customer name does not contain ioet ', () => { + spyOn(toastrServiceStub, 'error'); + const result = component.requiredFieldsForInternalAppExist('customer', 'Project Name'); + expect(toastrServiceStub.error).not.toHaveBeenCalledWith(EMPTY_FIELDS_ERROR_MESSAGE); + expect(result).toBe(true); + }); + + it('should return true if customer name contain ioet and project name contain Safari Books', () => { + spyOn(toastrServiceStub, 'error'); + const result = component.requiredFieldsForInternalAppExist('customer', 'Safari Books'); + expect(toastrServiceStub.error).not.toHaveBeenCalledWith(EMPTY_FIELDS_ERROR_MESSAGE); + expect(result).toBe(true); + }); + + it('when a technology is added or removed and entry is valid then dispatch UpdateActiveEntry', () => { + component.newData = mockEntryOverlap; + component.activeEntry = entry; + component.setDataToUpdate(entry); + + spyOn(store, 'dispatch'); + + const addedTechnologies = ['react']; + component.onTechnologyUpdated(addedTechnologies); + expect(store.dispatch).toHaveBeenCalled(); + }); + + it('does not dispatch an action and shows error when onTechnologyUpdated is called and entry is not valid', () => { + component.newData = mockEntryOverlap; + component.activeEntry = entry; + component.setDataToUpdate(entry); + + spyOn(component, 'entryFormIsValidate').and.returnValue(false); + spyOn(store, 'dispatch'); + + const addedTechnologies = ['react']; + component.onTechnologyUpdated(addedTechnologies); + + expect(store.dispatch).not.toHaveBeenCalled(); + }); + }); + diff --git a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts index a5481281e..61ec4ac3c 100644 --- a/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts +++ b/src/app/modules/time-clock/components/entry-fields/entry-fields.component.ts @@ -1,7 +1,7 @@ import { ActivityManagementActionTypes } from './../../../activities-management/store/activity-management.actions'; import { EntryActionTypes, LoadActiveEntry } from './../../store/entry.actions'; import { filter } from 'rxjs/operators'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, ElementRef, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Store, ActionsSubject, select } from '@ngrx/store'; import { Activity, NewEntry } from '../../../shared/models'; @@ -9,13 +9,15 @@ import { ProjectState } from '../../../customer-management/components/projects/c import { TechnologyState } from '../../../shared/store/technology.reducers'; import { ActivityState, LoadActivities } from '../../../activities-management/store'; import * as entryActions from '../../store/entry.actions'; -import { get } from 'lodash'; +import { get, head } from 'lodash'; import * as moment from 'moment'; import { ToastrService } from 'ngx-toastr'; import { formatDate } from '@angular/common'; import { getTimeEntriesDataSource } from '../../store/entry.selectors'; import { DATE_FORMAT } from 'src/environments/environment'; import { Subscription } from 'rxjs'; +import { EMPTY_FIELDS_ERROR_MESSAGE } from 'src/app/modules/shared/messages'; +import { INTERNAL_APP_STRING, PROJECT_NAME_TO_SKIP } from 'src/app/modules/shared/internal-app-constants'; type Merged = TechnologyState & ProjectState & ActivityState; @@ -25,6 +27,8 @@ type Merged = TechnologyState & ProjectState & ActivityState; styleUrls: ['./entry-fields.component.scss'], }) export class EntryFieldsComponent implements OnInit, OnDestroy { + @ViewChild('autofocus') autofocus!: ElementRef; + entryForm: FormGroup; selectedTechnologies: string[] = []; activities: Activity[] = []; @@ -35,7 +39,8 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { loadActivitiesSubscription: Subscription; loadActiveEntrySubscription: Subscription; actionSetDateSubscription: Subscription; - + isCookieFeatureToggleActive: boolean; + isFeatureToggleActive: boolean; constructor( private formBuilder: FormBuilder, private store: Store, @@ -51,15 +56,20 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { }); } - ngOnInit(): void { + ngOnInit(): void { this.store.dispatch(new LoadActivities()); this.store.dispatch(new entryActions.LoadEntries(new Date().getMonth() + 1, new Date().getFullYear())); this.loadActivitiesSubscription = this.actionsSubject$ .pipe(filter((action: any) => action.type === ActivityManagementActionTypes.LOAD_ACTIVITIES_SUCCESS)) .subscribe((action) => { - this.activities = action.payload; + this.activities = action.payload + .filter((item) => item.status !== 'inactive') + .sort((a, b) => { + return a.name.localeCompare(b.name); + }); this.store.dispatch(new LoadActiveEntry()); }); + this.loadActiveEntrySubscription = this.actionsSubject$ .pipe( filter( @@ -91,7 +101,8 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { start_date: this.activeEntry.start_date, start_hour: formatDate(this.activeEntry.start_date, 'HH:mm', 'en'), }; - }); + this.activateFocus(); + }); } get activity_id() { return this.entryForm.get('activity_id'); @@ -99,6 +110,13 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { get start_hour() { return this.entryForm.get('start_hour'); } + + activateFocus() { + if (this.activities.length > 0 && this.entryForm.value.activity_id === head(this.activities).id) { + this.autofocus.nativeElement.focus(); + } + } + setDataToUpdate(entryData: NewEntry) { if (entryData) { this.entryForm.patchValue({ @@ -116,12 +134,28 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { } } + /** + * Makes activity mandatory when clocking in. + * Also makes uri or description mandatory if it is an internal app. + */ entryFormIsValidate() { - return this.entryForm.valid; + let customerName = ''; + let projectName = ''; + this.store.pipe(select(getTimeEntriesDataSource)).subscribe((ds) => { + const dataToUse = ds.data.find((item) => item.project_id === this.activeEntry.project_id); + customerName = dataToUse.customer_name; + projectName = dataToUse.project_name; + }); + if (!this.entryForm.valid) { + this.toastrService.error('Activity is required'); + } + return this.requiredFieldsForInternalAppExist(customerName, projectName) && this.entryForm.valid; } onSubmit() { - this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value })); + if (this.entryFormIsValidate()) { + this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value })); + } } onUpdateStartHour() { @@ -145,7 +179,10 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { return; } this.entryForm.patchValue({ start_date: newHourEntered }); - this.store.dispatch(new entryActions.UpdateCurrentOrLastEntry({ ...this.newData, ...this.entryForm.value })); + this.newData.update_last_entry_if_overlap = true; + if (this.entryFormIsValidate()) { + this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, ...this.entryForm.value })); + } this.showTimeInbuttons = false; } @@ -165,12 +202,10 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { this.showTimeInbuttons = false; } - onTechnologyAdded($event: string[]) { - this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event })); - } - - onTechnologyRemoved($event: string[]) { - this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event })); + onTechnologyUpdated($event: string[]) { + if (this.entryFormIsValidate()) { + this.store.dispatch(new entryActions.UpdateEntryRunning({ ...this.newData, technologies: $event })); + } } ngOnDestroy(): void { @@ -178,4 +213,26 @@ export class EntryFieldsComponent implements OnInit, OnDestroy { this.loadActiveEntrySubscription.unsubscribe(); this.actionSetDateSubscription.unsubscribe(); } + + /** + * Manages the conditions for requiring uri or description fields + * when clocking in an internal app. + */ + requiredFieldsForInternalAppExist(customerName: string, projectName: string) { + const emptyValue = ''; + const areEmptyValues = [this.entryForm.value.uri, this.entryForm.value.description].every( + (item) => item === emptyValue + ); + + const isInternalApp = customerName.includes(INTERNAL_APP_STRING); + const canSkipDescriptionAndURI = PROJECT_NAME_TO_SKIP.some((projectNameItem) => + projectName.includes(projectNameItem) + ); + + if (isInternalApp && areEmptyValues && !canSkipDescriptionAndURI) { + this.toastrService.error(EMPTY_FIELDS_ERROR_MESSAGE); + return false; + } + return true; + } } diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html index 459bcab96..2f85e583b 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.html @@ -1,41 +1,86 @@ + +
+ + + + + + + + + + + + +
+
+
- - - - -
-
- - - -
-
- -   - + [items]="listProjectsShowed" + (search)="onSearch($event)" + (change)="onSelect($event)" + (close)="loadActiveTimeEntry()"> + +
+
+ {{item.customer.name}} - + {{item.name}} +
+
+ +   + +
-
- - - -
-
- + +
diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts index e81dd76dc..1e553352f 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.spec.ts @@ -18,13 +18,14 @@ describe('ProjectListHoverComponent', () => { let store: MockStore; let mockProjectsSelector; const toastrServiceStub = { - error: (message?: string, title?: string, override?: Partial) => { } + error: (message?: string, title?: string, override?: Partial) => {}, }; const state = { projects: { projects: [], customerProjects: [{ id: 'id', name: 'name', description: 'description', project_type_id: '123' }], + recentProjects: [], isLoading: false, message: '', projectToEdit: undefined, @@ -39,18 +40,23 @@ describe('ProjectListHoverComponent', () => { isLoading: false, message: '', }, - }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ProjectListHoverComponent, FilterProjectPipe], - providers: [FormBuilder, provideMockStore({ initialState: state }), - { provide: ToastrService, useValue: toastrServiceStub }], - imports: [HttpClientTestingModule, AutocompleteLibModule], - }).compileComponents(); - store = TestBed.inject(MockStore); - mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); - })); + }; + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ProjectListHoverComponent, FilterProjectPipe], + providers: [ + FormBuilder, + provideMockStore({ initialState: state }), + { provide: ToastrService, useValue: toastrServiceStub }, + ], + imports: [HttpClientTestingModule, AutocompleteLibModule], + }).compileComponents(); + store = TestBed.inject(MockStore); + mockProjectsSelector = store.overrideSelector(getCustomerProjects, state.projects); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(ProjectListHoverComponent); @@ -64,6 +70,12 @@ describe('ProjectListHoverComponent', () => { it('dispatchs a CreateEntry action on clockIn', () => { component.activeEntry = null; + const activitiesMock = [{ + id: 'xyz', + name: 'test', + description : 'test1' + }]; + component.activities = activitiesMock; spyOn(store, 'dispatch'); component.clockIn(1, 'customer', 'project'); @@ -77,7 +89,9 @@ describe('ProjectListHoverComponent', () => { component.updateProject(1); - expect(store.dispatch).toHaveBeenCalledWith(new UpdateEntryRunning({ id: component.activeEntry.id, project_id: 1 })); + expect(store.dispatch).toHaveBeenCalledWith( + new UpdateEntryRunning({ id: component.activeEntry.id, project_id: 1 }) + ); }); it('displays a message when the activity_id is null', () => { @@ -113,15 +127,51 @@ describe('ProjectListHoverComponent', () => { expect(component.activeEntrySubscription.unsubscribe).toHaveBeenCalled(); }); + it('should set all parameters on ngOnInit', () => { + spyOn(store, 'dispatch'); + spyOn(store, 'pipe').and.returnValue(of([{ id: 'p1', customer: { name: 'customer', description: 'nomatter' }, name: 'xyz' }])); + + component.ngOnInit(); + + expect(component.listRecentProjects.length).toEqual(1); + }); + it('sets customer name and project name on setSelectedProject', () => { spyOn(component.projectsForm, 'setValue'); - component.activeEntry = { project_id : 'p1'}; - component.listProjects = [{ id: 'p1', customer_name: 'customer', name: 'xyz' }]; + component.activeEntry = { project_id: 'p1' }; + component.listProjects = [{ id: 'p1', customer: { name: 'customer', description: 'nomatter' }, name: 'xyz' }]; component.setSelectedProject(); - expect(component.projectsForm.setValue) - .toHaveBeenCalledWith({ project_id: 'customer - xyz'}); + expect(component.projectsForm.setValue).toHaveBeenCalledWith({ project_id: 'customer - xyz' }); + }); + + it('should change projects showed to recent projects list when search input is empty on onSearch', () => { + const recentProjects = [{ id: '1', customer: { name: 'customer'}, name: 'xyz' }]; + const search = {term: '', items: []}; + component.listRecentProjects = recentProjects; + component.onSearch(search); + + expect(component.listProjectsShowed).toEqual(recentProjects); + }); + + it('should change projects showed to projects list when search input is not empty on onSearch', () => { + const listProjects = [{id: '1', customer: { name: 'customer'}, name: 'xyz' }]; + const search = {term: 'xyz', items: []}; + component.listProjects = listProjects; + component.onSearch(search); + + expect(component.listProjectsShowed).toEqual(listProjects); + }); + + it('should clock in when select a project on onSelect', () => { + const [id, customer, name] = ['1', 'customer', 'xyz']; + const projectSelected = { id, customer: { name: customer}, name }; + spyOn(component, 'clockIn'); + component.showClockIn = true; + component.onSelect(projectSelected); + + expect(component.clockIn).toHaveBeenCalledWith(id, customer, name); }); // TODO Fix this test since it is throwing this error diff --git a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts index c238d63bf..301aa33d5 100644 --- a/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts +++ b/src/app/modules/time-clock/components/project-list-hover/project-list-hover.component.ts @@ -8,53 +8,79 @@ import { Project } from 'src/app/modules/shared/models'; import * as actions from '../../../customer-management/components/projects/components/store/project.actions'; import { ProjectState } from '../../../customer-management/components/projects/components/store/project.reducer'; import * as entryActions from '../../store/entry.actions'; -import { getIsLoading, getProjects } from './../../../customer-management/components/projects/components/store/project.selectors'; +import { + getIsLoading, + getProjects, + getRecentProjects, +} from './../../../customer-management/components/projects/components/store/project.selectors'; import { EntryActionTypes } from './../../store/entry.actions'; import { getActiveTimeEntry } from './../../store/entry.selectors'; +import { Activity, } from '../../../shared/models'; +import { LoadActivities } from './../../../activities-management/store/activity-management.actions'; +import { allActivities } from 'src/app/modules/activities-management/store/activity-management.selectors'; +import { head } from 'lodash'; + @Component({ selector: 'app-project-list-hover', templateUrl: './project-list-hover.component.html', styleUrls: ['./project-list-hover.component.scss'], }) export class ProjectListHoverComponent implements OnInit, OnDestroy { - keyword = 'search_field'; listProjects: Project[] = []; + listRecentProjects: Project[] = []; + listProjectsShowed: Project[] = []; + activities: Activity[] = []; activeEntry; projectsForm: FormGroup; showClockIn: boolean; updateEntrySubscription: Subscription; isLoading$: Observable; projectsSubscription: Subscription; + recentProjectsSubscription: Subscription; activeEntrySubscription: Subscription; + loadActivitiesSubscription: Subscription; - constructor(private formBuilder: FormBuilder, private store: Store, - private actionsSubject$: ActionsSubject, private toastrService: ToastrService) { - this.projectsForm = this.formBuilder.group({ project_id: null, }); + constructor( + private formBuilder: FormBuilder, + private store: Store, + private actionsSubject$: ActionsSubject, + private toastrService: ToastrService + ) { + this.projectsForm = this.formBuilder.group({ project_id: null }); this.isLoading$ = this.store.pipe(delay(0), select(getIsLoading)); } - ngOnInit(): void { + ngOnInit(): void { this.store.dispatch(new actions.LoadProjects()); const projects$ = this.store.pipe(select(getProjects)); this.projectsSubscription = projects$.subscribe((projects) => { this.listProjects = []; projects.forEach((project) => { - const projectWithSearchField = {...project}; - projectWithSearchField.search_field = `${project.customer_name} - ${project.name}`; - this.listProjects.push(projectWithSearchField); - } - ); + const projectWithSearchField = { ...project }; + projectWithSearchField.search_field = `${project.customer.name} - ${project.name}`; + this.listProjects.push(projectWithSearchField); + }); this.loadActiveTimeEntry(); }); - this.updateEntrySubscription = this.actionsSubject$.pipe( - filter((action: any) => ( - action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS - ) - ) - ).subscribe((action) => { - this.activeEntry = action.payload; - this.setSelectedProject(); + this.store.dispatch(new LoadActivities()); + const activities$ = this.store.pipe(select(allActivities)); + activities$.subscribe((response) => { + this.activities = response; + }); + + this.store.dispatch(new actions.LoadRecentProjects()); + const recentProjects$ = this.store.pipe(select(getRecentProjects)); + this.recentProjectsSubscription = recentProjects$.subscribe((projects) => { + this.listRecentProjects = projects; + this.listProjectsShowed = this.listRecentProjects; }); + + this.updateEntrySubscription = this.actionsSubject$ + .pipe(filter((action: any) => action.type === EntryActionTypes.UPDATE_ENTRY_SUCCESS)) + .subscribe((action) => { + this.activeEntry = action.payload; + this.setSelectedProject(); + }); } loadActiveTimeEntry() { @@ -72,30 +98,46 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { }); } setSelectedProject() { - this.listProjects.forEach( (project) => { + this.listProjects.forEach((project) => { if (project.id === this.activeEntry.project_id) { - this.projectsForm.setValue( - { project_id: `${project.customer_name} - ${project.name}`, } - ); + this.projectsForm.setValue({ project_id: `${project.customer.name} - ${project.name}` }); } }); } clockIn(selectedProject, customerName, name) { + + // Debounce 'Clock In' buttons + const buttons = document.getElementsByClassName('btn btn-sm btn-primary btn-select'); + for (const button of buttons) { + button.setAttribute('disabled', 'true'); + } + setTimeout(() => { + for (const button of buttons) { + button.removeAttribute('disabled'); + } + }, 3000); + const entry = { project_id: selectedProject, start_date: new Date().toISOString(), timezone_offset: new Date().getTimezoneOffset(), - technologies: [] + technologies: [], + activity_id: head(this.activities).id, }; + this.store.dispatch(new entryActions.ClockIn(entry)); - this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } ); + this.projectsForm.setValue({ project_id: `${customerName} - ${name}` }); + setTimeout(() => { + this.store.dispatch(new actions.LoadRecentProjects()); + }, 2000); } updateProject(selectedProject) { const entry = { id: this.activeEntry.id, project_id: selectedProject }; this.store.dispatch(new entryActions.UpdateEntryRunning(entry)); this.store.dispatch(new entryActions.LoadActiveEntry()); + this.store.dispatch(new actions.LoadRecentProjects()); } switch(selectedProject, customerName, name) { @@ -103,12 +145,28 @@ export class ProjectListHoverComponent implements OnInit, OnDestroy { this.toastrService.error('Before switching, please select an activity'); } else { this.store.dispatch(new entryActions.SwitchTimeEntry(this.activeEntry.id, selectedProject)); - this.projectsForm.setValue( { project_id: `${customerName} - ${name}`, } ); + setTimeout(() => { + this.store.dispatch(new actions.LoadRecentProjects()); + }, 2000); + this.projectsForm.setValue({ project_id: `${customerName} - ${name}` }); + } + } + + onSearch({term}){ + const isSearchEmpty = (term === ''); + this.listProjectsShowed = isSearchEmpty ? this.listRecentProjects : this.listProjects; + } + + onSelect(project){ + if (project && this.showClockIn) { + this.clockIn(project.id, project.customer.name, project.name); + this.listProjectsShowed = this.listRecentProjects; } } ngOnDestroy(): void { this.projectsSubscription.unsubscribe(); + this.recentProjectsSubscription.unsubscribe(); this.activeEntrySubscription.unsubscribe(); this.updateEntrySubscription.unsubscribe(); } diff --git a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.css b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.css index e69de29bb..d35cd57e4 100644 --- a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.css +++ b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.css @@ -0,0 +1,30 @@ +.current-elapsed-time-box{ + border-color: #FF5E0A; + border-width: 2px; + background-color: #FF5E0A; + color: white; + border-radius: .25rem; +} + +.c-title{ + position: relative; + bottom: 9px; +} + +.clock-out{ + text-align: center; +} + +.bt-clock-out{ + background: #00baee; + color: #fff; + padding: 7px 20px; +} + +.bt-clock-out:hover{ + background: #10a5ce; + box-shadow: 0px 1px 5px 2px rgba(0,0,0,0.56); + -webkit-box-shadow: 0px 1px 5px 2px rgba(0,0,0,0.56); + -moz-box-shadow: 0px 1px 5px 2px rgba(0,0,0,0.56); + color: #fff; +} \ No newline at end of file diff --git a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.html b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.html index 04c77a9d6..5b58b4171 100644 --- a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.html +++ b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.html @@ -1,22 +1,38 @@ -
Summary
-
-
-
-
+
+
+

+ You clocked in at + {{ activeTimeEntry?.start_date | date:'shortTime' }} +

+
Current

{{ currentWorkingTime }}

-
+
+
+ +
+
+ +
Summary
+
+
+ +
+
Day

{{ timeEntriesSummary?.day | timeDetails }}

-
+
Week

{{ timeEntriesSummary?.week | timeDetails }}

-
+
Month

{{ timeEntriesSummary?.month | timeDetails }}

-
+ +
\ No newline at end of file diff --git a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.spec.ts b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.spec.ts index 00b5c9fab..b93b5d033 100644 --- a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.spec.ts +++ b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.spec.ts @@ -2,6 +2,7 @@ import { waitForAsync, ComponentFixture, discardPeriodicTasks, fakeAsync, TestBe import { ActionsSubject } from '@ngrx/store'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { interval } from 'rxjs'; +import { TotalHours } from 'src/app/modules/reports/models/total-hours-report'; import { Entry } from 'src/app/modules/shared/models/entry.model'; import { TimeDetails, TimeEntriesSummary } from './../../models/time.entry.summary'; import { TimeDetailsPipe } from './../../pipes/time-details.pipe'; @@ -36,6 +37,7 @@ describe('TimeEntriesSummaryComponent', () => { const state: EntryState = { active: timeEntry, isLoading: false, + resultSumEntriesSelected: new TotalHours(), message: '', createError: false, updateError: false, @@ -148,4 +150,11 @@ describe('TimeEntriesSummaryComponent', () => { discardPeriodicTasks(); })); + it('clockOut should emits event', () => { + spyOn(component.clockoutEvent, 'emit'); + component.clockOut(); + + expect(component.clockoutEvent.emit).toHaveBeenCalled(); + }); + }); diff --git a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.ts b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.ts index 9a225d605..b46c9b88a 100644 --- a/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.ts +++ b/src/app/modules/time-clock/components/time-entries-summary/time-entries-summary.component.ts @@ -5,9 +5,10 @@ import { Entry } from './../../../shared/models/entry.model'; import { TimeEntriesSummary } from '../../models/time.entry.summary'; import { LoadEntriesSummary, LoadActiveEntry, EntryActionTypes } from './../../store/entry.actions'; import { EntryState } from './../../store/entry.reducer'; -import { Store, ActionsSubject } from '@ngrx/store'; -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Store, ActionsSubject, select } from '@ngrx/store'; +import { Component, OnInit, OnDestroy, Output, EventEmitter, Input } from '@angular/core'; import * as moment from 'moment'; +import { getActiveTimeEntry } from '../../store/entry.selectors'; @Component({ selector: 'app-time-entries-summary', @@ -16,6 +17,10 @@ import * as moment from 'moment'; }) export class TimeEntriesSummaryComponent implements OnInit, OnDestroy { + @Input() activeTimeEntry: Entry; + @Input() areFieldsVisible = false; + @Output() clockoutEvent = new EventEmitter(); + timeEntriesSummary: TimeEntriesSummary; currentWorkingTime: string; destroyed$ = new Subject(); @@ -103,4 +108,9 @@ export class TimeEntriesSummaryComponent implements OnInit, OnDestroy { }); } } + + clockOut(): void { + this.clockoutEvent.emit(); + } + } diff --git a/src/app/modules/time-clock/pages/time-clock.component.html b/src/app/modules/time-clock/pages/time-clock.component.html index ed1a377f0..fdc2342de 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.html +++ b/src/app/modules/time-clock/pages/time-clock.component.html @@ -1,13 +1,9 @@ - +
-

- You clocked in at - {{ activeTimeEntry?.start_date | date:'shortTime' }} -

Hi {{ username }}, please select a project to clock-in.

@@ -19,7 +15,4 @@
-
- -
diff --git a/src/app/modules/time-clock/pages/time-clock.component.spec.ts b/src/app/modules/time-clock/pages/time-clock.component.spec.ts index 10a4b5959..b3687de5a 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.spec.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.spec.ts @@ -1,17 +1,26 @@ import { of } from 'rxjs'; -import { FormBuilder } from '@angular/forms'; -import { StopTimeEntryRunning, EntryActionTypes, LoadEntriesSummary } from './../store/entry.actions'; +import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { provideMockStore, MockStore } from '@ngrx/store/testing'; +import { NgxMaterialTimepickerModule } from 'ngx-material-timepicker'; +import { NgSelectModule } from '@ng-select/ng-select'; +import { ToastrService } from 'ngx-toastr'; +import { ActionsSubject } from '@ngrx/store'; +import { SocialAuthService } from 'angularx-social-login'; + +import { StopTimeEntryRunning, EntryActionTypes, LoadEntriesSummary } from './../store/entry.actions'; import { TimeClockComponent } from './time-clock.component'; import { ProjectState } from '../../customer-management/components/projects/components/store/project.reducer'; import { ProjectListHoverComponent } from '../components'; import { FilterProjectPipe } from '../../shared/pipes'; import { AzureAdB2CService } from '../../login/services/azure.ad.b2c.service'; -import { ActionsSubject } from '@ngrx/store'; import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.component'; -import { ToastrService } from 'ngx-toastr'; +import { LoginService } from '../../login/services/login.service'; +import { TechnologiesComponent } from '../../shared/components/technologies/technologies.component'; +import { TimeEntriesSummaryComponent } from '../components/time-entries-summary/time-entries-summary.component'; +import { TimeDetailsPipe } from '../pipes/time-details.pipe'; + describe('TimeClockComponent', () => { let component: TimeClockComponent; @@ -27,7 +36,7 @@ describe('TimeClockComponent', () => { const state = { projects: { - projects: [{ id: 'id', name: 'name', project_type_id: '' }], + projects: [{ id: 'id', name: 'name', project_type_id: '', customer: { name: 'customer', description: '' } }], customerProjects: [{ id: 'id', name: 'name', description: 'description' }], isLoading: false, }, @@ -49,20 +58,33 @@ describe('TimeClockComponent', () => { }, }; - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - imports: [HttpClientTestingModule], - declarations: [TimeClockComponent, ProjectListHoverComponent, FilterProjectPipe, EntryFieldsComponent], - providers: [ - FormBuilder, - AzureAdB2CService, - provideMockStore({ initialState: state }), - { provide: ActionsSubject, useValue: actionSub }, - { provide: ToastrService, useValue: toastrService }, - ], - }).compileComponents(); - store = TestBed.inject(MockStore); - })); + const socialAuthServiceStub = jasmine.createSpyObj('SocialAuthService', ['authState']); + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, NgSelectModule, NgxMaterialTimepickerModule, FormsModule, ReactiveFormsModule], + declarations: [ + TimeClockComponent, + ProjectListHoverComponent, + FilterProjectPipe, + EntryFieldsComponent, + TechnologiesComponent, + TimeEntriesSummaryComponent, + TimeDetailsPipe + ], + providers: [ + FormBuilder, + AzureAdB2CService, + LoginService, + provideMockStore({ initialState: state }), + { provide: ActionsSubject, useValue: actionSub }, + { provide: ToastrService, useValue: toastrService }, + { provide: SocialAuthService, useValue: socialAuthServiceStub } + ], + }).compileComponents(); + store = TestBed.inject(MockStore); + }) + ); beforeEach(() => { fixture = TestBed.createComponent(TimeClockComponent); @@ -79,7 +101,7 @@ describe('TimeClockComponent', () => { it('on STOP_TIME_ENTRY_RUNNING_SUCCESS summaries are reloaded', () => { const actionSubject = TestBed.inject(ActionsSubject) as ActionsSubject; const action = { - type: EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS + type: EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS, }; spyOn(store, 'dispatch'); @@ -107,6 +129,7 @@ describe('TimeClockComponent', () => { }); it('onInit checks if isLogin and gets the userName', () => { + component.isProduction = true; spyOn(azureAdB2CService, 'isLogin').and.returnValue(true); spyOn(azureAdB2CService, 'getName').and.returnValue('Name'); component.ngOnInit(); @@ -115,6 +138,7 @@ describe('TimeClockComponent', () => { }); it('onInit does not get the name if isLogin false', () => { + component.isProduction = true; spyOn(azureAdB2CService, 'isLogin').and.returnValue(false); spyOn(azureAdB2CService, 'getName').and.returnValue('Name'); component.ngOnInit(); @@ -122,6 +146,12 @@ describe('TimeClockComponent', () => { expect(azureAdB2CService.getName).toHaveBeenCalledTimes(0); }); + it('if activeTimeEntry is not defined, areFieldsVisible must be false', () => { + spyOn(store, 'pipe').and.returnValue(of(null)); + component.ngOnInit(); + expect(component.areFieldsVisible).toBe(false); + }); + it('stopEntry dispatch a StopTimeEntryRunning action', () => { spyOn(store, 'dispatch'); @@ -137,12 +167,11 @@ describe('TimeClockComponent', () => { expect(store.dispatch).toHaveBeenCalledWith(new StopTimeEntryRunning('id')); }); - it('clockOut set error Activity is required', () => { + it('do not dispatch if Activity is missing', () => { spyOn(store, 'dispatch'); - spyOn(injectedToastrService, 'error'); spyOn(component.entryFieldsComponent, 'entryFormIsValidate').and.returnValue(false); component.clockOut(); - expect(injectedToastrService.error).toHaveBeenCalled(); + expect(store.dispatch).not.toHaveBeenCalled(); }); }); diff --git a/src/app/modules/time-clock/pages/time-clock.component.ts b/src/app/modules/time-clock/pages/time-clock.component.ts index 30cbbb994..4a215f15a 100644 --- a/src/app/modules/time-clock/pages/time-clock.component.ts +++ b/src/app/modules/time-clock/pages/time-clock.component.ts @@ -10,6 +10,10 @@ import { EntryFieldsComponent } from '../components/entry-fields/entry-fields.co import { Entry } from './../../shared/models/entry.model'; import { EntryActionTypes, LoadEntriesSummary, StopTimeEntryRunning } from './../store/entry.actions'; import { getActiveTimeEntry } from './../store/entry.selectors'; +import { LoginService } from '../../login/services/login.service'; +import { environment } from 'src/environments/environment'; +import { EnvironmentType } from 'src/environments/enum'; + @Component({ selector: 'app-time-clock', templateUrl: './time-clock.component.html', @@ -23,16 +27,24 @@ export class TimeClockComponent implements OnInit, OnDestroy { activeTimeEntry: Entry; clockOutSubscription: Subscription; storeSubscription: Subscription; + isProduction = environment.production === EnvironmentType.TT_PROD_LEGACY; constructor( private azureAdB2CService: AzureAdB2CService, private store: Store, private toastrService: ToastrService, - private actionsSubject$: ActionsSubject + private actionsSubject$: ActionsSubject, + private loginService: LoginService ) {} ngOnInit(): void { - this.username = this.azureAdB2CService.isLogin() ? this.azureAdB2CService.getName() : ''; + if (this.isProduction) { + this.username = this.azureAdB2CService.isLogin() ? this.azureAdB2CService.getName() : ''; + }else{ + this.loginService.isLogin().subscribe(isLogin => { + this.username = isLogin ? this.loginService.getName() : ''; + }); + } this.storeSubscription = this.store.pipe(select(getActiveTimeEntry)).subscribe((activeTimeEntry) => { this.activeTimeEntry = activeTimeEntry; if (this.activeTimeEntry) { @@ -65,7 +77,6 @@ export class TimeClockComponent implements OnInit, OnDestroy { this.stopEntry(); } else { this.entryFieldsComponent.entryForm.get('activity_id').markAsTouched(); - this.toastrService.error('Activity is required'); } } diff --git a/src/app/modules/time-clock/services/entry.service.spec.ts b/src/app/modules/time-clock/services/entry.service.spec.ts index ca2630120..16166b9b0 100644 --- a/src/app/modules/time-clock/services/entry.service.spec.ts +++ b/src/app/modules/time-clock/services/entry.service.spec.ts @@ -10,12 +10,14 @@ import * as moment from 'moment'; describe('EntryService', () => { let service: EntryService; let httpMock: HttpTestingController; + let reportsUrl: any; beforeEach(() => { TestBed.configureTestingModule({imports: [HttpClientTestingModule], providers: [DatePipe]}); service = TestBed.inject(EntryService); httpMock = TestBed.inject(HttpTestingController); service.baseUrl = 'time-entries'; + reportsUrl = service.urlInProductionLegacy ? service.baseUrl : service.baseUrl + '/report/'; }); it('services are ready to be used', inject( @@ -34,7 +36,7 @@ describe('EntryService', () => { expect(response.length).toBe(1); }); - const createEntryRequest = httpMock.expectOne(service.baseUrl); + const createEntryRequest = httpMock.expectOne(`${service.baseUrl}/`); expect(createEntryRequest.request.method).toBe('POST'); createEntryRequest.flush(entry); }); @@ -42,14 +44,14 @@ describe('EntryService', () => { it('loads an activeEntry with /running', () => { service.loadActiveEntry().subscribe(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/running`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/running/`); expect(loadEntryRequest.request.method).toBe('GET'); }); it('loads summary with get /summary?time_offset=', () => { service.summary().subscribe(); const timeOffset = new Date().getTimezoneOffset(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary?time_offset=${timeOffset}`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/summary/?time_offset=${timeOffset}`); expect(loadEntryRequest.request.method).toBe('GET'); }); @@ -60,7 +62,7 @@ describe('EntryService', () => { const timezoneOffset = new Date().getTimezoneOffset(); service.loadEntries({ year, month }).subscribe(); - const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}?month=${month}&year=${year}&timezone_offset=${timezoneOffset}`); + const loadEntryRequest = httpMock.expectOne(`${service.baseUrl}/?month=${month}&year=${year}&timezone_offset=${timezoneOffset}`); expect(loadEntryRequest.request.method).toBe('GET'); }); @@ -84,22 +86,30 @@ describe('EntryService', () => { }); it('stops an entry using POST', () => { + service.urlInProductionLegacy = true; service.stopEntryRunning('id').subscribe(); - const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/id/stop`); + const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/id/stop/`); expect(updateEntryRequest.request.method).toBe('POST'); }); + it('stops an entry using PUT', () => { + service.urlInProductionLegacy = false; + service.stopEntryRunning('id').subscribe(); + + const updateEntryRequest = httpMock.expectOne(`${service.baseUrl}/stop/`); + expect(updateEntryRequest.request.method).toBe('PUT'); + }); + it('when getting time entries for report, time range should be sent', () => { const yesterday = moment(new Date()).subtract(1, 'day'); const today = moment(new Date()); const pipe: DatePipe = new DatePipe('en'); const timeRange: TimeEntriesTimeRange = {start_date: yesterday, end_date: today}; const userId = '123'; - service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); expect(loadEntryRequest.request.params.get('start_date')).toBe(pipe.transform(yesterday, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT)); expect(loadEntryRequest.request.params.get('end_date')).toBe(pipe.transform(today, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT)); @@ -111,10 +121,8 @@ describe('EntryService', () => { const today = moment(new Date()); const timeRange: TimeEntriesTimeRange = { start_date: yesterday, end_date: today }; const userId = '123'; - service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); expect(loadEntryRequest.request.params.get('limit')).toEqual('9999'); }); @@ -123,11 +131,8 @@ describe('EntryService', () => { const today = moment(new Date()); const timeRange: TimeEntriesTimeRange = { start_date: yesterday, end_date: today }; const userId = '123'; - service.loadEntriesByTimeRange(timeRange, userId).subscribe(); - - const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === service.baseUrl); - + const loadEntryRequest = httpMock.expectOne(req => req.method === 'GET' && req.url === reportsUrl); const timezoneOffset = new Date().getTimezoneOffset().toString(); expect(loadEntryRequest.request.params.get('timezone_offset')).toEqual(timezoneOffset); }); @@ -143,10 +148,13 @@ describe('EntryService', () => { it('entries are found by project id with a limit 2 by default', () => { const projectId = 'project-id'; + const startDate = (moment().subtract(1, 'months')).format(); + const endDate = moment().format(); service.findEntriesByProjectId(projectId).subscribe(); - const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}?limit=2&project_id=${projectId}`); + const restartEntryRequest = httpMock.expectOne( `${service.baseUrl}/?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`); expect(restartEntryRequest.request.method).toBe('GET'); }); + }); diff --git a/src/app/modules/time-clock/services/entry.service.ts b/src/app/modules/time-clock/services/entry.service.ts index 2506b4a5b..feda2f9d6 100644 --- a/src/app/modules/time-clock/services/entry.service.ts +++ b/src/app/modules/time-clock/services/entry.service.ts @@ -4,36 +4,51 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { environment } from './../../../../environments/environment'; +import { EnvironmentType } from './../../../../environments/enum'; import { TimeEntriesTimeRange } from '../models/time-entries-time-range'; import { DatePipe } from '@angular/common'; import { Entry } from '../../shared/models'; +import * as moment from 'moment'; + + +interface QueryParams { + start_date: string; + end_date: string; + user_id: string | string[]; + limit: string; + timezone_offset: string; + project_id?: string; + activity_id?: string; +} + +export const MAX_NUMBER_OF_ENTRIES_FOR_REPORTS = 9999; + @Injectable({ providedIn: 'root', }) export class EntryService { - - constructor(private http: HttpClient, private datePipe: DatePipe) { - } + constructor(private http: HttpClient, private datePipe: DatePipe) {} static TIME_ENTRIES_DATE_TIME_FORMAT = 'yyyy-MM-ddTHH:mm:ssZZZZZ'; baseUrl = `${environment.timeTrackerApiUrl}/time-entries`; + urlInProductionLegacy = environment.production === EnvironmentType.TT_PROD_LEGACY; loadActiveEntry(): Observable { - return this.http.get(`${this.baseUrl}/running`); + return this.http.get(`${this.baseUrl}/running/`); } loadEntries(date): Observable { const timezoneOffset = new Date().getTimezoneOffset(); - return this.http.get(`${this.baseUrl}?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); + return this.http.get(`${this.baseUrl}/?month=${date.month}&year=${date.year}&timezone_offset=${timezoneOffset}`); } createEntry(entryData): Observable { - return this.http.post(this.baseUrl, entryData); + return this.http.post(`${this.baseUrl}/`, entryData); } updateEntry(entryData): Observable { - const {id} = entryData; + const { id } = entryData; return this.http.put(`${this.baseUrl}/${id}`, entryData); } @@ -43,8 +58,9 @@ export class EntryService { } stopEntryRunning(idEntry: string): Observable { - const url = `${this.baseUrl}/${idEntry}/stop`; - return this.http.post(url, null); + return this.urlInProductionLegacy + ? this.http.post(`${this.baseUrl}/${idEntry}/stop/`, null) + : this.http.put(`${this.baseUrl}/stop/`, null); } restartEntry(idEntry: string): Observable { @@ -54,27 +70,46 @@ export class EntryService { summary(): Observable { const timeOffset = new Date().getTimezoneOffset(); - const summaryUrl = `${this.baseUrl}/summary?time_offset=${timeOffset}`; + const summaryUrl = `${this.baseUrl}/summary/?time_offset=${timeOffset}`; return this.http.get(summaryUrl); } findEntriesByProjectId(projectId: string): Observable { - const findEntriesByProjectURL = `${this.baseUrl}?limit=2&project_id=${projectId}`; + const startDate = this.getDateLastMonth(); + const endDate = this.getCurrentDate(); + const findEntriesByProjectURL = `${this.baseUrl}/?limit=2&project_id=${projectId}&start_date=${startDate}&end_date=${endDate}`; return this.http.get(findEntriesByProjectURL); } - loadEntriesByTimeRange(range: TimeEntriesTimeRange, userId: string): Observable { - const MAX_NUMBER_OF_ENTRIES_FOR_REPORTS = 9999; - return this.http.get(this.baseUrl, - { - params: { - start_date: this.datePipe.transform(range.start_date, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT), - end_date: this.datePipe.transform(range.end_date, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT), - user_id: userId, - limit: `${MAX_NUMBER_OF_ENTRIES_FOR_REPORTS}`, - timezone_offset : new Date().getTimezoneOffset().toString(), - } - } - ); + + loadEntriesByTimeRange( + range: TimeEntriesTimeRange, + userId: string[] | string, + projectId?: string, + activityId?: string + ): Observable { + + const loadEntriesByTimeRangeURL = this.urlInProductionLegacy ? this.baseUrl : this.baseUrl + '/report/'; + const queryParams: QueryParams = { + start_date: this.datePipe.transform(range.start_date, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT), + end_date: this.datePipe.transform(range.end_date, EntryService.TIME_ENTRIES_DATE_TIME_FORMAT), + user_id: userId, + limit: `${MAX_NUMBER_OF_ENTRIES_FOR_REPORTS}`, + timezone_offset: new Date().getTimezoneOffset().toString(), + }; + if (projectId !== '*') {queryParams.project_id = projectId; } + if (activityId !== '*') {queryParams.activity_id = activityId; } + + return this.http.get(loadEntriesByTimeRangeURL, { + params: { ...queryParams }, + }); + } + + getDateLastMonth() { + return moment().subtract(1, 'months').format(); + } + + getCurrentDate() { + return moment().format(); } } diff --git a/src/app/modules/time-clock/store/entry.actions.ts b/src/app/modules/time-clock/store/entry.actions.ts index f58378ffa..734515c59 100644 --- a/src/app/modules/time-clock/store/entry.actions.ts +++ b/src/app/modules/time-clock/store/entry.actions.ts @@ -70,8 +70,7 @@ export class LoadEntriesSummary implements Action { export class LoadEntriesSummarySuccess implements Action { readonly type = EntryActionTypes.LOAD_ENTRIES_SUMMARY_SUCCESS; - constructor(readonly payload: TimeEntriesSummary) { - } + constructor(readonly payload: TimeEntriesSummary) {} } export class LoadEntriesSummaryFail implements Action { @@ -85,43 +84,37 @@ export class LoadActiveEntry implements Action { export class LoadActiveEntrySuccess implements Action { readonly type = EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS; - constructor(readonly payload) { - } + constructor(readonly payload) {} } export class LoadActiveEntryFail implements Action { public readonly type = EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class LoadEntries implements Action { public readonly type = EntryActionTypes.LOAD_ENTRIES; - constructor(public month: number, public year: number) { - } + constructor(public month: number, public year: number) {} } export class LoadEntriesSuccess implements Action { readonly type = EntryActionTypes.LOAD_ENTRIES_SUCCESS; - constructor(readonly payload: Entry[]) { - } + constructor(readonly payload: Entry[]) {} } export class LoadEntriesFail implements Action { public readonly type = EntryActionTypes.LOAD_ENTRIES_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class CreateEntry implements Action { public readonly type = EntryActionTypes.CREATE_ENTRY; - constructor(public payload: NewEntry) { - } + constructor(public payload: NewEntry) {} } export class CreateEntrySuccess implements Action { @@ -132,104 +125,89 @@ export class CreateEntrySuccess implements Action { export class CreateEntryFail implements Action { public readonly type = EntryActionTypes.CREATE_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class DeleteEntry implements Action { public readonly type = EntryActionTypes.DELETE_ENTRY; - constructor(public entryId: string) { - } + constructor(public entryId: string) {} } export class DeleteEntrySuccess implements Action { public readonly type = EntryActionTypes.DELETE_ENTRY_SUCCESS; - constructor(public entryId: string) { - } + constructor(public entryId: string) {} } export class DeleteEntryFail implements Action { public readonly type = EntryActionTypes.DELETE_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class UpdateEntryRunning implements Action { public readonly type = EntryActionTypes.UPDATE_ENTRY_RUNNING; - constructor(public payload) { - } + constructor(public payload) {} } export class UpdateEntry implements Action { public readonly type = EntryActionTypes.UPDATE_ENTRY; - constructor(public payload) { - } + constructor(public payload) {} } export class UpdateEntrySuccess implements Action { public readonly type = EntryActionTypes.UPDATE_ENTRY_SUCCESS; - constructor(public payload: Entry) { - } + constructor(public payload: Entry) {} } export class UpdateEntryFail implements Action { public readonly type = EntryActionTypes.UPDATE_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class UpdateCurrentOrLastEntry implements Action { public readonly type = EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY; - constructor(public payload) { - } + constructor(public payload) {} } export class UpdateCurrentOrLastEntryFail implements Action { public readonly type = EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export class StopTimeEntryRunning implements Action { public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING; - constructor(readonly payload: string) { - } + constructor(readonly payload: string) {} } export class StopTimeEntryRunningSuccess implements Action { public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING_SUCCESS; - constructor(readonly payload) { - } + constructor(readonly payload) {} } export class StopTimeEntryRunningFail implements Action { public readonly type = EntryActionTypes.STOP_TIME_ENTRY_RUNNING_FAILED; - constructor(public error: string) { - } + constructor(public error: string) {} } export class CleanEntryCreateError implements Action { public readonly type = EntryActionTypes.CLEAN_ENTRY_CREATE_ERROR; - constructor(public error: boolean) { - } + constructor(public error: boolean) {} } export class CleanEntryUpdateError implements Action { public readonly type = EntryActionTypes.CLEAN_ENTRY_UPDATE_ERROR; - constructor(public error: boolean) { - } + constructor(public error: boolean) {} } export class DefaultEntry implements Action { @@ -239,15 +217,18 @@ export class DefaultEntry implements Action { export class LoadEntriesByTimeRange implements Action { public readonly type = EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE; - constructor(readonly timeRange: TimeEntriesTimeRange, readonly userId: string = '*') { - } + constructor( + readonly timeRange: TimeEntriesTimeRange, + readonly userId: string = '*', + readonly projectId: string = '*', + readonly activityId: string = '*' + ) {} } export class LoadEntriesByTimeRangeSuccess implements Action { readonly type = EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE_SUCCESS; - constructor(readonly payload: Entry[]) { - } + constructor(readonly payload: Entry[]) {} } export class LoadEntriesByTimeRangeFail implements Action { @@ -257,22 +238,19 @@ export class LoadEntriesByTimeRangeFail implements Action { export class RestartEntry implements Action { readonly type = EntryActionTypes.RESTART_ENTRY; - constructor(readonly entry: Entry) { - } + constructor(readonly entry: Entry) {} } export class RestartEntrySuccess implements Action { readonly type = EntryActionTypes.RESTART_ENTRY_SUCCESS; - constructor(readonly payload: Entry) { - } + constructor(readonly payload: Entry) {} } export class RestartEntryFail implements Action { public readonly type = EntryActionTypes.RESTART_ENTRY_FAIL; - constructor(public error: string) { - } + constructor(public error: string) {} } export type EntryActions = diff --git a/src/app/modules/time-clock/store/entry.effects.spec.ts b/src/app/modules/time-clock/store/entry.effects.spec.ts index 7dd30863c..5a0b750b8 100644 --- a/src/app/modules/time-clock/store/entry.effects.spec.ts +++ b/src/app/modules/time-clock/store/entry.effects.spec.ts @@ -9,11 +9,12 @@ import * as moment from 'moment'; import { ToastrModule, ToastrService } from 'ngx-toastr'; import { Observable, of, throwError } from 'rxjs'; import { TimeEntriesTimeRange } from '../models/time-entries-time-range'; -import { EntryService } from '../services/entry.service'; +import { EntryService, MAX_NUMBER_OF_ENTRIES_FOR_REPORTS } from '../services/entry.service'; import { INFO_SAVED_SUCCESSFULLY } from './../../shared/messages'; import { EntryActionTypes, SwitchTimeEntry, DeleteEntry, CreateEntry } from './entry.actions'; import { EntryEffects } from './entry.effects'; + describe('TimeEntryActionEffects', () => { let actions$: Observable; @@ -23,9 +24,7 @@ describe('TimeEntryActionEffects', () => { const entry: Entry = { project_id: 'p-id', start_date: new Date(), id: 'id' }; const dateTest = moment().format('YYYY-MM-DD'); - const endHourTest = moment().subtract(5, 'hours').format('HH:mm:ss'); const startHourTest = moment().subtract(3, 'hours').format('HH:mm:ss'); - const endDateTest = new Date(`${dateTest}T${endHourTest.trim()}`); const startDateTest = new Date(`${dateTest}T${startHourTest.trim()}`); const entryUpdate = { @@ -121,7 +120,11 @@ describe('TimeEntryActionEffects', () => { it('returns a LOAD_ACTIVE_ENTRY_SUCCESS when the entry that is running it is in the same day', async () => { actions$ = of({ type: EntryActionTypes.LOAD_ACTIVE_ENTRY }); const serviceSpy = spyOn(service, 'loadActiveEntry'); - serviceSpy.and.returnValue(of(entry)); + const mockEntry = { + ...entry, + start_date: new Date() + }; + serviceSpy.and.returnValue(of(mockEntry)); effects.loadActiveEntry$.subscribe(action => { expect(action.type).toEqual(EntryActionTypes.LOAD_ACTIVE_ENTRY_SUCCESS); @@ -151,6 +154,15 @@ describe('TimeEntryActionEffects', () => { }); }); + it('LoadActiveEntryFail if an entry active is not found', async () => { + actions$ = of({ type: EntryActionTypes.LOAD_ACTIVE_ENTRY }); + const serviceSpy = spyOn(service, 'loadActiveEntry'); + serviceSpy.and.returnValue(of(null)); + effects.loadActiveEntry$.subscribe(action => { + expect(action.type).toEqual(EntryActionTypes.LOAD_ACTIVE_ENTRY_FAIL); + }); + }); + it('display a success message on UPDATE_ENTRY', async () => { actions$ = of({ type: EntryActionTypes.UPDATE_ENTRY, entry }); spyOn(toastrService, 'success'); @@ -177,7 +189,7 @@ describe('TimeEntryActionEffects', () => { spyOn(toastrService, 'success'); effects.updateEntryRunning$.subscribe(action => { - expect(toastrService.success).toHaveBeenCalledTimes(0); + expect(toastrService.success).toHaveBeenCalledTimes(1); expect(action.type).toBe(EntryActionTypes.UPDATE_ENTRY_SUCCESS); }); }); @@ -355,21 +367,26 @@ describe('TimeEntryActionEffects', () => { it('should update last entry when UPDATE_CURRENT_OR_LAST_ENTRY is executed', async () => { actions$ = of({ type: EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY, payload: entry }); - spyOn(service, 'loadEntries').and.returnValue(of([entry, entry])); - + const lastEntryMock: Entry = { + ...entry, + end_date: moment(entry.start_date).add(4, 'h').toDate(), + }; + spyOn(service, 'loadEntries').and.returnValue(of([entry, lastEntryMock])); effects.updateCurrentOrLastEntry$.subscribe(action => { expect(action.type).toEqual(EntryActionTypes.UPDATE_ENTRY); }); }); it('should update current entry when UPDATE_CURRENT_OR_LAST_ENTRY is executed', async () => { - const lastEntry: Entry = { project_id: 'p-id', start_date: new Date(), id: 'id', end_date: endDateTest}; + const makeDateYear = new Date(); + makeDateYear.setMonth(makeDateYear.getMonth() - 1); + const lastEntry: Entry = { project_id: 'p-id', start_date: new Date(), id: 'id', end_date: makeDateYear}; actions$ = of({ type: EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY, payload: entryUpdate }); spyOn(service, 'loadEntries').and.returnValue(of([lastEntry, lastEntry])); spyOn(toastrService, 'success'); effects.updateCurrentOrLastEntry$.subscribe(action => { - expect(toastrService.success).toHaveBeenCalledWith('You change the time-in successfully'); + expect(toastrService.success).toHaveBeenCalledWith('You changed the time-in successfully'); expect(action.type).toEqual(EntryActionTypes.UPDATE_ENTRY_RUNNING); }); }); @@ -382,4 +399,27 @@ describe('TimeEntryActionEffects', () => { expect(action.type).toEqual(EntryActionTypes.UPDATE_CURRENT_OR_LAST_ENTRY_FAIL); }); }); + + it('should show a warning when maximum number of entries is received', async () => { + const timeRange: TimeEntriesTimeRange = { start_date: moment(new Date()), end_date: moment(new Date()) }; + const userId = '*'; + const entries = []; + for (let i = 0; i < MAX_NUMBER_OF_ENTRIES_FOR_REPORTS; i++){ + entries.push({...entry, id: i.toString() }); + } + + const serviceSpy = spyOn(service, 'loadEntriesByTimeRange'); + serviceSpy.and.returnValue(of(entries)); + spyOn(toastrService, 'warning'); + + actions$ = of({ type: EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE, timeRange, userId }); + + effects.loadEntriesByTimeRange$.subscribe(action => { + expect(toastrService.warning).toHaveBeenCalledWith( + 'Still loading. Limit of ' + MAX_NUMBER_OF_ENTRIES_FOR_REPORTS + + ' entries reached, try filtering the request.' + + ' Some information may be missing.' + ); + }); + }); }); diff --git a/src/app/modules/time-clock/store/entry.effects.ts b/src/app/modules/time-clock/store/entry.effects.ts index 43d73cc4f..180893600 100644 --- a/src/app/modules/time-clock/store/entry.effects.ts +++ b/src/app/modules/time-clock/store/entry.effects.ts @@ -5,7 +5,7 @@ import { Action } from '@ngrx/store'; import { ToastrService } from 'ngx-toastr'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; -import { EntryService } from '../services/entry.service'; +import { EntryService, MAX_NUMBER_OF_ENTRIES_FOR_REPORTS } from '../services/entry.service'; import * as actions from './entry.actions'; import * as moment from 'moment'; @@ -23,6 +23,7 @@ export class EntryEffects { const stopDateForEntry = new Date(response.end_date); stopDateForEntry.setSeconds(stopDateForEntry.getSeconds() + 1); const entry = { + activity_id: response.activity_id, project_id: action.idProjectSwitching, start_date: stopDateForEntry.toISOString(), timezone_offset: new Date().getTimezoneOffset(), @@ -44,6 +45,9 @@ export class EntryEffects { mergeMap(() => this.entryService.summary().pipe( map((response) => { + if (!response){ + this.toastrService.warning('It is a brand new month! You do not have any time entries yet.'); + } return new actions.LoadEntriesSummarySuccess(response); }), catchError((error) => { @@ -142,7 +146,7 @@ export class EntryEffects { if (error.status === 404) { return of(new actions.CreateEntry(entry)); } else { - this.toastrService.error('We could not clock in you, try again later.'); + this.toastrService.error('We could not clock you in, try again later.'); return of(new actions.CreateEntryFail('Error')); } }) @@ -193,6 +197,7 @@ export class EntryEffects { mergeMap((entry) => this.entryService.updateEntry(entry).pipe( map((entryResponse) => { + this.toastrService.success(INFO_SAVED_SUCCESSFULLY); return new actions.UpdateEntrySuccess(entryResponse); }), catchError((error) => { @@ -236,7 +241,7 @@ export class EntryEffects { if (isStartTimeInLastEntry) { return new actions.UpdateEntry({ id: lastEntry.id, end_date: entry.start_date }); } else { - this.toastrService.success('You change the time-in successfully'); + this.toastrService.success('You changed the time-in successfully'); return new actions.UpdateEntryRunning(entry); } }), @@ -253,8 +258,15 @@ export class EntryEffects { ofType(actions.EntryActionTypes.LOAD_ENTRIES_BY_TIME_RANGE), map((action: actions.LoadEntriesByTimeRange) => action), mergeMap((action) => - this.entryService.loadEntriesByTimeRange(action.timeRange, action.userId).pipe( + this.entryService.loadEntriesByTimeRange(action.timeRange, action.userId, action.projectId, action.activityId).pipe( map((response) => { + if (response.length >= MAX_NUMBER_OF_ENTRIES_FOR_REPORTS){ + this.toastrService.warning( + 'Still loading. Limit of ' + MAX_NUMBER_OF_ENTRIES_FOR_REPORTS + + ' entries reached, try filtering the request.' + + ' Some information may be missing.' + ); + } return new actions.LoadEntriesByTimeRangeSuccess(response); }), catchError((error) => { diff --git a/src/app/modules/time-clock/store/entry.reducer.spec.ts b/src/app/modules/time-clock/store/entry.reducer.spec.ts index dafce2ee7..eb0f1a7a5 100644 --- a/src/app/modules/time-clock/store/entry.reducer.spec.ts +++ b/src/app/modules/time-clock/store/entry.reducer.spec.ts @@ -1,3 +1,4 @@ +import { TotalHours } from '../../reports/models/total-hours-report'; import { TimeDetails, TimeEntriesSummary } from '../models/time.entry.summary'; import { Entry, NewEntry } from './../../shared/models'; import * as actions from './entry.actions'; @@ -15,6 +16,7 @@ describe('entryReducer', () => { message: '', createError: null, updateError: null, + resultSumEntriesSelected: new TotalHours(), timeEntriesSummary: emptyTimeEntriesSummary, timeEntriesDataSource: { data: [], @@ -72,7 +74,7 @@ describe('entryReducer', () => { expect(state.isLoading).toBe(true); }); - it('on Default, ', () => { + it('on DefaultAction, state equal to initial state', () => { const action = new actions.DefaultEntry(); const state = entryReducer(initialState, action); expect(state).toEqual(initialState); @@ -136,6 +138,30 @@ describe('entryReducer', () => { expect(state.isLoading).toEqual(true); }); + it('sort previous entries when a new one is entered', () => { + const newState: EntryState = { ...initialState, timeEntriesDataSource: { + data: [ + { + project_id: '123', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const entryCreated: Entry = { ...entry }; + entryCreated.end_date = null; + const action = new actions.CreateEntrySuccess(entryCreated); + const state = entryReducer(newState, action); + expect(state.active).toBe(entryCreated); + }); + it('on CreateEntrySuccess, if end_date is null then it is the active entry', () => { const entryCreated: Entry = { ...entry }; entryCreated.end_date = null; @@ -188,6 +214,28 @@ describe('entryReducer', () => { expect(state.timeEntriesDataSource.data).toEqual([]); }); + it('filter reportDataSource data when one is to be deleted', () => { + const newState: EntryState = { ...initialState, reportDataSource: { + data: [ + { + project_id: '123456', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const action = new actions.DeleteEntrySuccess('idxxx'); + const state = entryReducer(newState, action); + expect(state.reportDataSource.data.length).toEqual(1); + }); + it('on LoadEntriesFail, active tobe null', () => { const action = new actions.DeleteEntryFail('error'); const state = entryReducer(initialState, action); @@ -224,6 +272,39 @@ describe('entryReducer', () => { expect(state.isLoading).toEqual(false); }); + it('sort the list of entries when one is updated', () => { + const newState: EntryState = { ...initialState, timeEntriesDataSource: { + data: [ + { + project_id: '123', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + }, + { + project_id: '123456', + description: 'description', + technologies: ['angular', 'javascript'], + uri: 'uri', + id: 'id', + start_date: new Date(), + end_date: new Date(), + activity_id: 'activity', + project_name: 'time-tracker' + } + ], + isLoading: false, + }}; + const action = new actions.UpdateEntrySuccess(entry); + const state = entryReducer(newState, action); + expect(state.isLoading).toEqual(false); + }); + it('on cleanEntryCreateError, createError to be null', () => { const action = new actions.CleanEntryCreateError(null); const state = entryReducer(initialState, action); diff --git a/src/app/modules/time-clock/store/entry.reducer.ts b/src/app/modules/time-clock/store/entry.reducer.ts index 1fa6e3171..023328efb 100644 --- a/src/app/modules/time-clock/store/entry.reducer.ts +++ b/src/app/modules/time-clock/store/entry.reducer.ts @@ -2,10 +2,12 @@ import { Entry } from '../../shared/models'; import { DataSource } from '../../shared/models/data-source.model'; import { TimeDetails, TimeEntriesSummary } from '../models/time.entry.summary'; import { EntryActions, EntryActionTypes } from './entry.actions'; +import { TotalHours } from '../../reports/models/total-hours-report'; export interface EntryState { active: Entry; isLoading: boolean; + resultSumEntriesSelected: TotalHours; message: string; createError: boolean; updateError: boolean; @@ -20,6 +22,7 @@ const emptyTimeEntriesSummary: TimeEntriesSummary = { day: emptyTimeDetails, wee export const initialState = { active: null, isLoading: false, + resultSumEntriesSelected: new TotalHours(), message: '', createError: null, updateError: null, @@ -263,6 +266,7 @@ export const entryReducer = (state: EntryState = initialState, action: EntryActi return { ...state, isLoading: true, + resultSumEntriesSelected: {hours: 0, minutes: 0, seconds: 0}, reportDataSource: { data: [], isLoading: true diff --git a/src/app/modules/time-clock/store/entry.selectors.spec.ts b/src/app/modules/time-clock/store/entry.selectors.spec.ts index d3fa7c708..1d5c088c0 100644 --- a/src/app/modules/time-clock/store/entry.selectors.spec.ts +++ b/src/app/modules/time-clock/store/entry.selectors.spec.ts @@ -1,3 +1,4 @@ +import { TotalHours } from '../../reports/models/total-hours-report'; import { Entry } from '../../shared/models'; import { TimeDetails, TimeEntriesSummary } from '../models/time.entry.summary'; import * as selectors from './entry.selectors'; @@ -52,4 +53,11 @@ describe('Entry selectors', () => { expect(selectors.getUpdateError.projector(entryState)).toEqual(error); }); + + it('should select resultSumEntriesSelected', () => { + const resultSumEntriesSelected: TotalHours = { hours: 0, minutes: 0, seconds: 0 }; + const entryState = { resultSumEntriesSelected }; + + expect(selectors.getResultSumEntriesSelected.projector(entryState)).toEqual(resultSumEntriesSelected); + }); }); diff --git a/src/app/modules/time-clock/store/entry.selectors.ts b/src/app/modules/time-clock/store/entry.selectors.ts index 2655ac3e8..fb29e8e8a 100644 --- a/src/app/modules/time-clock/store/entry.selectors.ts +++ b/src/app/modules/time-clock/store/entry.selectors.ts @@ -16,3 +16,5 @@ export const getStatusMessage = createSelector(getEntryState, (state: EntryState export const getReportDataSource = createSelector(getEntryState, (state: EntryState) => state?.reportDataSource); export const getTimeEntriesDataSource = createSelector(getEntryState, (state: EntryState) => state?.timeEntriesDataSource); + +export const getResultSumEntriesSelected = createSelector(getEntryState, (state: EntryState) => state?.resultSumEntriesSelected); diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.html b/src/app/modules/time-entries/components/calendar/calendar.component.html new file mode 100644 index 000000000..3be76abcc --- /dev/null +++ b/src/app/modules/time-entries/components/calendar/calendar.component.html @@ -0,0 +1,168 @@ + +
+

+ {{ timeEntries.project_name }} +

+
+

+ {{ timeEntries.activity_name }} +

+

+ + {{ timeEntries.start_date | date: 'HH:mm' }} - + {{ timeEntries.end_date | date: 'HH:mm' }} +

+
+
+

+ {{ timeEntries.description }} +

+
+
+
+ + +
+ +
+
+ + +
+ {{ day.date | calendarDate: 'monthViewDayNumber':locale }} +
+
+ + +
+
+ + +
+ + +
+
+ +
+
+
+
+ + + +
+
+
+

+ {{ currentDate | date: 'EEEE' }} +

+

+ {{ currentDate | date: 'MMM d' }} +

+
+
+
+ + + +
+
+
+
+ + + + + + +
+
diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.scss b/src/app/modules/time-entries/components/calendar/calendar.component.scss new file mode 100644 index 000000000..01c04c092 --- /dev/null +++ b/src/app/modules/time-entries/components/calendar/calendar.component.scss @@ -0,0 +1,171 @@ +@import '/src/styles/colors.scss'; +@import '/src/styles/vars.scss'; + +$line-height-value: $line-height-base * $font-size-base; + +$max-lines-project: 2; +$max-lines-activity: 1; +$max-lines-description: 50; +$container-time-entries-margin: 0.125; +$time-entries-padding: 0.5rem; +$project-name-line-height: 1; + +$project-text-size: $max-lines-project * $project-name-line-height; +$activity-text-size: $max-lines-activity * $line-height-base; + +@function calculate-border-color($base-background-color) { + @return darken(desaturate(adjust-hue($base-background-color, 6), 18), 27); +} + +@function calculate-bold-text-color($text-color) { + @return darken(saturate(adjust-hue($text-color, 6), 46.19), 40.98); +} + +.switch-calendar-view { + height: calc(100vh - 320px); + overflow-y: auto; +} + +::-webkit-scrollbar { + width: 5px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: transparent; +} + +::-webkit-scrollbar-thumb:hover { + background: #888; + border-radius: 5px; +} + +@for $card-height from 12 through $max-lines-description { + $wrap-height: ($card-height * 0.625rem) - + $project-text-size - + $activity-text-size - + $container-time-entries-margin - + $time-entries-padding; + $line-clamp: ($wrap-height / $line-height-value); + .line-#{$card-height} { + overflow-y: hidden; + height: $wrap-height; + display: -webkit-box; + -webkit-line-clamp: round($line-clamp); + -webkit-box-orient: vertical; + } +} + +.container-time-entries { + margin: $container-time-entries-margin; + + div.time-entries { + padding: $time-entries-padding; + height: 100%; + border-radius: 7px; + overflow-y: hidden; + color: $primary-text; + margin: 2px 6px 0px 5px; + overflow-wrap: anywhere; + background-color: $background-card-entry; + border-left: 5px solid calculate-border-color($background-card-entry); + -webkit-box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); + -moz-box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); + box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); + + p { + margin: 0px; + } + + p.project-name { + color: calculate-bold-text-color($primary-text); + line-height: 1; + display: -webkit-box; + -webkit-line-clamp: $max-lines-project; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + } + + p.additional { + overflow-wrap: anywhere; + color: calculate-bold-text-color($primary-text); + } + } + + div.running-entry { + border-left: 5px solid calculate-border-color($background-card-entry-activate); + background-color: $background-card-entry-activate; + color: $activate-text; + + p.project-name { + color: calculate-bold-text-color($activate-text); + } + + p.additional { + color: calculate-bold-text-color($activate-text); + } + } +} + +.container-time-entries-adapt-height { + height: calc(100% - 2px); + min-height: 40px; +} + +.close-icon { + font-size: 0.7em; + width: 20px; + height: 20px; + border-radius: 10px; + color: white; + top: -5px; + right: -5px; + -webkit-box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); + -moz-box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); + box-shadow: 0px 2px 5px 0px lighten($shadow-card-entry, 50%); +} + +.currentDate { + p { + margin: 0; + } + padding-right: 0px; +} + +@media (max-width: 576px) { + .custom-right-buttons-group { + text-align: center; + } + .custom-left-buttons-group { + text-align: center; + } +} + +@media (min-width: 576px) { + .custom-right-buttons-group { + text-align: end; + } + .custom-left-buttons-group { + text-align: start; + } +} + +.btn-navigation { + padding-left: 6px; + padding-right: 6px; +} + +@media only screen and (min-width: 576px) and (max-width: 767px) { + .currentDate { + margin-left: 14px; + p { + margin: 0; + } + padding-right: 0px; + } +} diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.spec.ts b/src/app/modules/time-entries/components/calendar/calendar.component.spec.ts new file mode 100644 index 000000000..5347e912d --- /dev/null +++ b/src/app/modules/time-entries/components/calendar/calendar.component.spec.ts @@ -0,0 +1,372 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { CalendarEvent, CalendarModule, CalendarView, DateAdapter } from 'angular-calendar'; +import * as moment from 'moment'; +import { Observable, of } from 'rxjs'; +import { Entry } from 'src/app/modules/shared/models'; +import { DataSource } from 'src/app/modules/shared/models/data-source.model'; +import { MatNativeDateModule } from '@angular/material/core'; +import { CommonModule } from '@angular/common'; +import { adapterFactory } from 'angular-calendar/date-adapters/date-fns'; + +import { CalendarComponent } from './calendar.component'; + + +type MockCardEntryHeight = { + startDate: string; + endDate: string; + expected: number; +}; + +type MockEntryVisibleCurrentDate = { + current: string; + initial: string; + expected: boolean; +}; + +describe('CalendarComponent', () => { + let component: CalendarComponent; + let fixture: ComponentFixture; + let currentDate: moment.Moment; + let fakeEntry: Entry; + let fakeEntryRunning: Entry; + let mockCardEntriesHeight: MockCardEntryHeight[]; + let mockEntriesVisibleCurrentDate: MockEntryVisibleCurrentDate[]; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + MatNativeDateModule, + CommonModule, + CalendarModule.forRoot({ + provide: DateAdapter, + useFactory: adapterFactory, + }) + ], + declarations: [ CalendarComponent ], + }).compileComponents(); + mockCardEntriesHeight = [ + { startDate: '2021-04-11T08:00:00Z', endDate: '2021-04-11T10:20:00Z', expected: 28 }, + { startDate: '2021-04-12T17:00:00Z', endDate: '2021-04-12T17:00:00Z', expected: 0 }, + { startDate: '2021-04-11T18:00:00Z', endDate: '2021-04-12T18:00:00Z', expected: 288 }, + { startDate: '2021-04-12T12:00:00Z', endDate: '2021-04-12T12:01:01Z', expected: 0 }, + ]; + mockEntriesVisibleCurrentDate = [ + { current: '2021-04-11T10:20:00Z', initial: '2021-04-11T08:00:00Z', expected: true }, + { current: '2021-04-12T17:00:00Z', initial: '2021-04-11T17:00:00Z', expected: false }, + { current: '2021-04-11T18:00:00Z', initial: '2021-04-12T18:00:00Z', expected: false }, + { current: '2021-04-12T12:00:00Z', initial: '2021-04-12T12:00:00Z', expected: true }, + ]; + currentDate = moment(); + fakeEntry = { + id: 'entry_1', + project_id: 'abc', + project_name: 'Time-tracker', + start_date: new Date('2020-02-05T15:36:15.887Z'), + end_date: new Date('2020-02-05T18:36:15.887Z'), + customer_name: 'ioet Inc.', + activity_id: 'development', + technologies: ['Angular', 'TypeScript'], + description: 'No comments', + uri: 'EY-25', + }; + fakeEntryRunning = { + id: 'entry_1', + project_id: 'abc', + project_name: 'Time-tracker', + start_date: new Date('2020-02-05T15:36:15.887Z'), + end_date: null, + customer_name: 'ioet Inc.', + activity_id: 'development', + technologies: ['Angular', 'TypeScript'], + description: 'No comments', + uri: 'EY-25', + }; + + jasmine.clock().mockDate(currentDate.toDate()); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(CalendarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('call castEntryToCalendarEvent when set timeEntries$', () => { + const fakeTimeEntries = new Observable>(); + spyOn(component, 'castEntryToCalendarEvent'); + + component.timeEntries$ = fakeTimeEntries; + + expect(component.castEntryToCalendarEvent).toHaveBeenCalledWith(fakeTimeEntries); + }); + + it('set [] in timeEntriesAsEvent when call castEntryToCalendarEvent and timeEntries is empty', () => { + const expectedtimeEntriesAsEvent = []; + const fakeTimeEntries = new Observable>(); + + component.castEntryToCalendarEvent(fakeTimeEntries); + + expect(component.timeEntriesAsEvent).toEqual(expectedtimeEntriesAsEvent); + }); + + it('set [entry] in timeEntriesAsEvent when call castEntryToCalendarEvent and timeEntries is not empty', () => { + const fakeTimeEntryAsEvent: CalendarEvent = { + start: fakeEntry.start_date, + end: fakeEntry.end_date, + title: fakeEntry.description, + id: fakeEntry.id, + meta: fakeEntry, + }; + const fakeDatasource = { + isLoading: false, + data: [fakeEntry], + }; + const fakeTimeEntries = of(fakeDatasource); + const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent]; + + component.castEntryToCalendarEvent(fakeTimeEntries); + + expect(component.timeEntriesAsEvent).toEqual(expectedtimeEntriesAsEvent); + expect(component.timeEntriesAsEvent.length).toEqual(1); + }); + + it('set [entry] without endDate in timeEntriesAsEvent when call castEntryToCalendarEvent and timeEntries is not empty', () => { + const fakeTimeEntryAsEvent: CalendarEvent = { + start: fakeEntryRunning.start_date, + end: fakeEntryRunning.end_date, + title: fakeEntryRunning.description, + id: fakeEntryRunning.id, + meta: fakeEntryRunning, + }; + const fakeDatasource = { + isLoading: false, + data: [fakeEntryRunning], + }; + const fakeTimeEntries = of(fakeDatasource); + const expectedtimeEntriesAsEvent = [fakeTimeEntryAsEvent]; + + component.castEntryToCalendarEvent(fakeTimeEntries); + + expect(component.timeEntriesAsEvent).toEqual(expectedtimeEntriesAsEvent); + expect(component.timeEntriesAsEvent.length).toEqual(1); + }); + + it('Call isVisibleForCurrentDate when call ngOnInit()', () => { + spyOn(component, 'isVisibleForCurrentDate'); + + component.ngOnInit(); + + expect(component.isVisibleForCurrentDate).toHaveBeenCalled(); + }); + + it('Call navigationEnable when call ngOnInit()', () => { + spyOn(component, 'navigationEnable'); + + component.ngOnInit(); + + expect(component.navigationEnable).toHaveBeenCalledWith(CalendarView.Month); + }); + + it('emit time entry id when call handleEditEvent', () => { + const fakeTimeEntryAsEvent: CalendarEvent = { + start: fakeEntry.start_date, + end: fakeEntry.end_date, + title: fakeEntry.description, + id: fakeEntry.id, + meta: fakeEntry, + }; + const fakeValueEmit = { + id: fakeEntry.id, + }; + spyOn(component.viewModal, 'emit'); + + component.handleEditEvent(fakeTimeEntryAsEvent); + + expect(component.viewModal.emit).toHaveBeenCalledWith(fakeValueEmit); + }); + + it('emit time entry meta when call handleDeleteEvent', () => { + const fakeTimeEntryAsEvent: CalendarEvent = { + start: fakeEntry.start_date, + end: fakeEntry.end_date, + title: fakeEntry.description, + id: fakeEntry.id, + meta: fakeEntry, + }; + const fakeValueEmit = { + timeEntry: fakeEntry, + }; + spyOn(component.deleteTimeEntry, 'emit'); + + component.handleDeleteEvent(fakeTimeEntryAsEvent); + + expect(component.deleteTimeEntry.emit).toHaveBeenCalledWith(fakeValueEmit); + }); + + it('emit current date and call navigationEnable when call handleChangeDateEvent', () => { + const calendarView: CalendarView = CalendarView.Month; + const fakeValueEmit = { + date: currentDate.toDate() + }; + spyOn(component, 'navigationEnable'); + spyOn(component.changeDate, 'emit'); + spyOn(component, 'isVisibleForCurrentDate'); + + component.handleChangeDateEvent(); + + expect(component.navigationEnable).toHaveBeenCalledWith(calendarView); + expect(component.isVisibleForCurrentDate).toHaveBeenCalled(); + expect(component.changeDate.emit).toHaveBeenCalledWith(fakeValueEmit); + }); + + it('set incoming calendarView in calendarView when call changeCalendarView', () => { + const fakeCalendarView: CalendarView = CalendarView.Day; + + component.changeCalendarView(CalendarView.Day); + + expect(component.calendarView).toEqual(fakeCalendarView); + }); + + it('emit calendarView Day when call changeCalendarView', () => { + const fakeCalendarView: CalendarView = CalendarView.Day; + component.calendarView = CalendarView.Month; + spyOn(component.changeView, 'emit'); + component.changeCalendarView(fakeCalendarView); + expect(component.changeView.emit).toHaveBeenCalledWith({ calendarView: fakeCalendarView }); + }); + + it('set srcoll to current time marker in calendarView when is call scrollToCurrentTimeMarker', () => { + const fakeCalendarView: CalendarView = CalendarView.Week; + spyOn(component, 'scrollToCurrentTimeMarker'); + component.changeCalendarView(fakeCalendarView); + expect(component.scrollToCurrentTimeMarker).toHaveBeenCalled(); + }); + + it('set false in nextDateDisabled when call navigationEnable and calendarView != Month and currentDate + 1 day is not greater to initialDate', () => { + component.currentDate = moment().subtract(2, 'day').toDate(); + component.initialDate = moment().toDate(); + + component.navigationEnable(CalendarView.Week); + + expect(component.nextDateDisabled).toBeFalse(); + }); + + it('false in nextDateDisabled when call navigationEnable and calendarView == Month and currentDate.month != initialDate.month', () => { + component.currentDate = moment().subtract(2, 'month').toDate(); + component.initialDate = moment().toDate(); + + component.navigationEnable(CalendarView.Month); + + expect(component.nextDateDisabled).toBeFalse(); + }); + + it('set false in nextDateDisabled when call navigationEnable and calendarView == Month and currentDate.year != initialDate.year', () => { + component.currentDate = moment().subtract(2, 'year').toDate(); + component.initialDate = moment().toDate(); + + component.navigationEnable(CalendarView.Month); + + expect(component.nextDateDisabled).toBeFalse(); + }); + + it('set true in nextDateDisabled when call navigationEnable and calendarView == Month and currentDate equal to initialDate', () => { + component.currentDate = moment().toDate(); + component.initialDate = moment().toDate(); + + component.navigationEnable(CalendarView.Month); + + expect(component.nextDateDisabled).toBeTrue(); + }); + + it('set true in nextDateDisabled when call navigationEnable and calendarView != Month and currentDate isGreater than initialDate', () => { + component.currentDate = moment().toDate(); + component.initialDate = moment().toDate(); + + component.navigationEnable(CalendarView.Week); + + expect(component.nextDateDisabled).toBeTrue(); + }); + + it('return 30 when call getTimeWork and end date is null', () => { + const expectedValue = 30; + + const response = component.getTimeWork(fakeEntryRunning.start_date, fakeEntryRunning.end_date); + + expect(response).toEqual(expectedValue); + }); + + it('return subtraction between start date an end date in minutes when call getTimeWork', () => { + const expectedValue = 20; + const minutesToBeAdded = 20; + fakeEntry.start_date = moment().toDate(); + fakeEntry.end_date = moment().add(minutesToBeAdded, 'minute').toDate(); + + const response = component.getTimeWork(fakeEntry.start_date, fakeEntry.end_date); + + expect(response).toEqual(expectedValue); + }); + + it('return true when call timeIsGreaterThan and subtraction between start date an end date is greater than incoming timeRange', () => { + const minutesToBeAdded = 20; + const timeRangeIncoming = 10; + fakeEntry.start_date = moment().toDate(); + fakeEntry.end_date = moment().add(minutesToBeAdded, 'minute').toDate(); + + const response = component.timeIsGreaterThan(fakeEntry.start_date, fakeEntry.end_date, timeRangeIncoming); + + expect(response).toBeTrue(); + }); + + it('return false when call timeIsGreaterThan and subtraction between start date an end date is less than incoming timeRange', () => { + const minutesToBeAdded = 10; + const timeRangeIncoming = 20; + fakeEntry.start_date = moment().toDate(); + fakeEntry.end_date = moment().add(minutesToBeAdded, 'minute').toDate(); + + const response = component.timeIsGreaterThan(fakeEntry.start_date, fakeEntry.end_date, timeRangeIncoming); + + expect(response).toBeFalse(); + }); + + it('return true when call isVisibleForCurrentView and currentCalendarView is equal to desiredView', () => { + const currentCalendarView: CalendarView = CalendarView.Week; + const desiredView: CalendarView = CalendarView.Week; + const response = component.isVisibleForCurrentView(currentCalendarView, [desiredView]); + + expect(response).toBeTrue(); + }); + + it('return false when call isVisibleForCurrentView and currentCalendarView is different to desiredView', () => { + const currentCalendarView: CalendarView = CalendarView.Day; + const desiredView: CalendarView = CalendarView.Week; + const response = component.isVisibleForCurrentView(currentCalendarView, [desiredView]); + + expect(response).toBeFalse(); + }); + + it('returns boolean when call isVisibleForCurrentDate', () => { + mockEntriesVisibleCurrentDate.forEach((item: MockEntryVisibleCurrentDate) => { + component.currentDate = new Date(item.current); + component.initialDate = new Date(item.initial); + const result = component.isVisibleForCurrentDate(); + + expect(result).toBe(item.expected); + }); + }); + + it('return card entry height multiplied by height variation when call getCardEntryHeight', () => { + mockCardEntriesHeight.forEach((item: MockCardEntryHeight) => { + const fakeStartDate = new Date(item.startDate); + const fakeEndDate = new Date(item.endDate); + const result = component.getCardEntryHeight(fakeStartDate, fakeEndDate); + + expect(result).toBe(item.expected); + }); + }); +}); diff --git a/src/app/modules/time-entries/components/calendar/calendar.component.ts b/src/app/modules/time-entries/components/calendar/calendar.component.ts new file mode 100644 index 000000000..594946ad6 --- /dev/null +++ b/src/app/modules/time-entries/components/calendar/calendar.component.ts @@ -0,0 +1,171 @@ +import { + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + OnInit, + Output, + ViewChild, +} from '@angular/core'; +import { CalendarEvent, CalendarView, DAYS_OF_WEEK } from 'angular-calendar'; +import { Observable } from 'rxjs'; +import * as moment from 'moment'; +import { DataSource } from '../../../shared/models/data-source.model'; +import { Entry } from 'src/app/modules/shared/models'; +import { map } from 'rxjs/operators'; +import { SubstractDatePipe } from 'src/app/modules/shared/pipes/substract-date/substract-date.pipe'; +import { differenceInMinutes, startOfDay, startOfHour } from 'date-fns'; + +@Component({ + selector: 'app-calendar', + templateUrl: './calendar.component.html', + styleUrls: ['./calendar.component.scss'], +}) +export class CalendarComponent implements OnInit { + readonly HALF_HOUR: number = 30; + readonly DEFAULT_HEADER_HEIGHT = 52; + readonly VARIATION_HEIGHT: number = 2; + readonly VISIBLE_TARGETS_FOR_TIME_ENTRIES_DESCRIPTION: CalendarView[] = [CalendarView.Week, CalendarView.Day]; + readonly CALENDAR_VIEW_ENUM: typeof CalendarView = CalendarView; + readonly WEEK_START_DAY = DAYS_OF_WEEK.MONDAY; + + @ViewChild('scrollContainer') scrollContainer: ElementRef; + + @Input() set timeEntries$(timeEntries: Observable>) { + this.castEntryToCalendarEvent(timeEntries); + } + + @Input() calendarView: CalendarView = CalendarView.Month; + @Input() currentDate: Date = new Date(); + + @Output() viewModal: EventEmitter = new EventEmitter(); + @Output() deleteTimeEntry: EventEmitter = new EventEmitter(); + @Output() changeDate: EventEmitter = new EventEmitter<{ + date: Date; + }>(); + @Output() changeView: EventEmitter = new EventEmitter<{ + calendarView: CalendarView; + }>(); + + initialDate: Date; + previusDate: Date; + isToday: boolean; + timeEntriesAsEvent: CalendarEvent[]; + nextDateDisabled: boolean; + + constructor(private referenceChangeDetector: ChangeDetectorRef) { + this.initialDate = new Date(); + this.previusDate = new Date(); + this.isToday = false; + this.timeEntriesAsEvent = []; + this.nextDateDisabled = true; + } + + ngOnInit(): void { + this.isToday = this.isVisibleForCurrentDate(); + this.navigationEnable(this.calendarView); + } + + scrollToCurrentTimeMarker() { + if (this.calendarView === CalendarView.Week || CalendarView.Day) { + const minutesSinceStartOfDay = differenceInMinutes(startOfHour(this.currentDate), startOfDay(this.currentDate)); + const headerHeight = this.calendarView === CalendarView.Week ? this.DEFAULT_HEADER_HEIGHT : 0; + this.scrollContainer.nativeElement.scrollTop = minutesSinceStartOfDay * this.VARIATION_HEIGHT + headerHeight; + } + } + + castEntryToCalendarEvent(timeEntries$: Observable>) { + timeEntries$ + .pipe( + map((timeEntriesDatasorce) => + timeEntriesDatasorce.data.map( + (timeEntries) => + ({ + start: new Date(timeEntries.start_date), + end: timeEntries.end_date ? new Date(timeEntries.end_date) : null, + title: timeEntries.description, + id: timeEntries.id, + meta: timeEntries, + } as CalendarEvent) + ) + ) + ) + .subscribe((timeEntriesAsEvent) => { + this.timeEntriesAsEvent = [...timeEntriesAsEvent].reverse(); + }); + } + + handleEditEvent(timeEntryAsEvent: CalendarEvent): void { + this.viewModal.emit({ + id: timeEntryAsEvent.id, + }); + } + + handleDeleteEvent(timeEntryAsEvent: CalendarEvent): void { + this.deleteTimeEntry.emit({ + timeEntry: timeEntryAsEvent.meta, + }); + } + + handleChangeDateEvent(): void { + const date = this.currentDate; + this.isToday = this.isVisibleForCurrentDate(); + this.navigationEnable(this.calendarView); + this.changeDate.emit({ date }); + } + + changeCalendarView(calendarView: CalendarView) { + this.calendarView = calendarView; + this.scrollContainer.nativeElement.scrollTop = 0; + if (this.calendarView !== CalendarView.Month) { + this.referenceChangeDetector.detectChanges(); + this.scrollToCurrentTimeMarker(); + } + this.changeView.emit({ calendarView }); + } + + navigationEnable(calendarView: CalendarView) { + let enable = false; + const currentDate = moment(this.currentDate); + const initialDate = moment(this.initialDate); + if (calendarView === CalendarView.Month) { + if (currentDate.month() === initialDate.month() && currentDate.year() === initialDate.year()) { + enable = true; + } + } + currentDate.add(1, 'day'); + if (currentDate > initialDate) { + enable = true; + } + this.nextDateDisabled = enable; + } + + getTimeWork(startDate: Date, endDate: Date): number { + if (!endDate) { + return 30; + } + return new SubstractDatePipe().transformInMinutes(endDate, startDate); + } + + getCardEntryHeight(startDate: Date, endDate: Date): number { + const heightCard = this.getTimeWork(startDate, endDate) * this.VARIATION_HEIGHT; + const finalHeightCard = heightCard / 10; + return Math.floor(finalHeightCard); + } + + timeIsGreaterThan(startDate: Date, endDate: Date, timeRange: number): boolean { + const timeWorkInMinutes = this.getTimeWork(startDate, endDate); + return timeWorkInMinutes > timeRange; + } + + isVisibleForCurrentView(currentCalendarView: CalendarView, desiredView: CalendarView[]): boolean { + return desiredView.includes(currentCalendarView); + } + + isVisibleForCurrentDate(): boolean { + const currentDate: Date = new Date(this.currentDate); + const initialDate: Date = new Date(this.initialDate); + return currentDate.setHours(0, 0, 0, 0) === initialDate.setHours(0, 0, 0, 0); + } +} diff --git a/src/app/modules/time-entries/pages/time-entries.component.html b/src/app/modules/time-entries/pages/time-entries.component.html index 9df5c7f0a..e0aca8556 100644 --- a/src/app/modules/time-entries/pages/time-entries.component.html +++ b/src/app/modules/time-entries/pages/time-entries.component.html @@ -1,92 +1,85 @@
-
-
- +
+ +
-
-
- -
+
+ +
- - - - - - - - - - - - - - - - - - - - - - - - -
DateTime in - outDurationCustomerProjectActivity
{{ entry.start_date | date: 'MM/dd/yyyy' }}{{ entry.start_date | date: 'HH:mm' }} - {{ entry.end_date | date: 'HH:mm' }}{{ entry.end_date | substractDate: entry.start_date }}{{ entry.customer_name }}{{ entry.project_name }}{{ entry.activity_name }} - - -
+ +
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
DateTime in - outDurationCustomerProjectActivityActions
{{ entry.start_date | date: 'MM/dd/yyyy' }}{{ dateTimeOffset.parseDateTimeOffset(entry.start_date, actualDate.getTimezoneOffset()) }} - {{ dateTimeOffset.parseDateTimeOffset(entry.end_date, actualDate.getTimezoneOffset()) }}{{ entry.end_date | substractDate: entry.start_date }}{{ entry.customer_name }}{{ entry.project_name }}{{ entry.activity_name }} + + +
+
+