diff --git a/.gitattributes b/.gitattributes index 91d3c46..5a2fbc7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,4 +12,9 @@ /phpstan.neon export-ignore /phpstan-baseline.neon export-ignore /phpunit.xml.dist export-ignore -/rector export-ignore +/rector.php export-ignore +/bin export-ignore +/babel.config.js export-ignore +/jest.config.js export-ignore +/rollup.config.js export-ignore +/tsconfig.json export-ignore diff --git a/.github/workflows/exported_files.yml b/.github/workflows/exported_files.yml new file mode 100644 index 0000000..7539f47 --- /dev/null +++ b/.github/workflows/exported_files.yml @@ -0,0 +1,18 @@ +name: Exported files + +on: [push] + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - name: "Checkout code" + uses: "actions/checkout@v4" + + - name: "Check exported files" + run: | + EXPECTED="LICENSE,README.md,RELEASES.md,SECURITY.md,composer.json,package.json" + CURRENT="$(git archive HEAD | tar --list --exclude="assets" --exclude="assets/*" --exclude="src" --exclude="src/*" | paste -s -d ",")" + echo "CURRENT =${CURRENT}" + echo "EXPECTED=${EXPECTED}" + test "${CURRENT}" == "${EXPECTED}" diff --git a/.gitignore b/.gitignore index 18e5996..b8e03bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ -/vendor/ +.phpunit.result.cache +*.cache +*.log +node_modules +package-lock.json /composer.lock -/.phpunit.cache +/vendor +/.phpunit.cache/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 4ec12c7..86be7ce 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/README.md b/README.md index 7c57fb0..0f57b00 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Progressive Web App for Symfony -=============================== +# Progressive Web App for Symfony ![Build Status](https://github.com/Spomky-Labs/pwa-bundle/workflows/Coding%20Standards/badge.svg) ![Build Status](https://github.com/Spomky-Labs/pwa-bundle/workflows/Static%20Analyze/badge.svg) @@ -24,7 +23,7 @@ Please have a look at the [Web app manifests](https://developer.mozilla.org/en-U # Installation -Install the bundle with Composer: +Install the bundle with Composer: ```bash composer require spomky-labs/pwa-bundle @@ -42,9 +41,9 @@ I bring solutions to your problems and answer your questions. If you really love that project and the work I have done or if you want I prioritize your issues, then you can help me out for a couple of :beers: or more! -* [Become a sponsor](https://github.com/sponsors/Spomky) -* [Become a Patreon](https://www.patreon.com/FlorentMorselli) -* [Buy me a coffee](https://www.buymeacoffee.com/FlorentMorselli) +- [Become a sponsor](https://github.com/sponsors/Spomky) +- [Become a Patreon](https://www.patreon.com/FlorentMorselli) +- [Buy me a coffee](https://www.buymeacoffee.com/FlorentMorselli) # Contributing @@ -58,7 +57,7 @@ Please make sure to [follow these best practices](.github/CONTRIBUTING.md). # Security Issues If you discover a security vulnerability within the project, please **don't use the bug tracker and don't publish it publicly**. -Instead, all security issues must be sent to security [at] spomky-labs.com. +Instead, all security issues must be sent to security [at] spomky-labs.com. # Licence diff --git a/RELEASES.md b/RELEASES.md index b3f2355..66861f3 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -3,6 +3,6 @@ ## Supported Versions | Version | Supported | -|---------|--------------------| +| ------- | ------------------ | | 1.0.x | :white_check_mark: | | < 1.0.x | :x: | diff --git a/assets/dist/controller.d.ts b/assets/dist/controller.d.ts new file mode 100644 index 0000000..a7c69a2 --- /dev/null +++ b/assets/dist/controller.d.ts @@ -0,0 +1,21 @@ +import { Controller } from '@hotwired/stimulus'; +export default class extends Controller { + static targets: string[]; + static values: { + onlineMessage: { + type: StringConstructor; + default: string; + }; + offlineMessage: { + type: StringConstructor; + default: string; + }; + }; + readonly onlineMessageValue: string; + readonly offlineMessageValue: string; + readonly attributeTargets: HTMLElement[]; + readonly messageTargets: HTMLElement[]; + connect(): void; + dispatchEvent(name: any, payload: any): void; + statusChanged(data: any): void; +} diff --git a/assets/dist/controller.js b/assets/dist/controller.js new file mode 100644 index 0000000..598dc2f --- /dev/null +++ b/assets/dist/controller.js @@ -0,0 +1,55 @@ +import { Controller } from '@hotwired/stimulus'; + +var Status; +(function (Status) { + Status["OFFLINE"] = "OFFLINE"; + Status["ONLINE"] = "ONLINE"; +})(Status || (Status = {})); +class default_1 extends Controller { + connect() { + this.dispatchEvent('connect', {}); + if (navigator.onLine) { + this.statusChanged({ + status: Status.ONLINE, + message: this.onlineMessageValue, + }); + } + else { + this.statusChanged({ + status: Status.OFFLINE, + message: this.offlineMessageValue, + }); + } + window.addEventListener("offline", () => { + this.statusChanged({ + status: Status.OFFLINE, + message: this.offlineMessageValue, + }); + }); + window.addEventListener("online", () => { + this.statusChanged({ + status: Status.ONLINE, + message: this.onlineMessageValue, + }); + }); + } + dispatchEvent(name, payload) { + this.dispatch(name, { detail: payload, prefix: 'connection-status' }); + } + statusChanged(data) { + this.messageTargets.forEach((element) => { + element.innerHTML = data.message; + }); + this.attributeTargets.forEach((element) => { + element.setAttribute('data-connection-status', data.status); + }); + this.dispatchEvent('status-changed', { detail: data }); + } +} +default_1.targets = ['message', 'attribute']; +default_1.values = { + onlineMessage: { type: String, default: 'You are online.' }, + offlineMessage: { type: String, default: 'You are offline.' }, +}; + +export { default_1 as default }; diff --git a/assets/jest.config.js b/assets/jest.config.js new file mode 100644 index 0000000..a7fde9b --- /dev/null +++ b/assets/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../jest.config.js'); diff --git a/assets/package.json b/assets/package.json new file mode 100644 index 0000000..a3a4152 --- /dev/null +++ b/assets/package.json @@ -0,0 +1,28 @@ +{ + "name": "@pwa/connection-status", + "description": "PWA for Symfony", + "license": "MIT", + "version": "1.0.0", + "main": "dist/controller.js", + "types": "dist/controller.d.ts", + "symfony": { + "controllers": { + "connection-status": { + "main": "dist/controller.js", + "name": "pwa/connection-status", + "webpackMode": "eager", + "fetch": "eager", + "enabled": true + } + }, + "importmap": { + "@hotwired/stimulus": "^3.0.0" + } + }, + "peerDependencies": { + "@hotwired/stimulus": "^3.0.0" + }, + "devDependencies": { + "@hotwired/stimulus": "^3.0.0" + } +} diff --git a/assets/src/controller.ts b/assets/src/controller.ts new file mode 100644 index 0000000..bf83be2 --- /dev/null +++ b/assets/src/controller.ts @@ -0,0 +1,61 @@ +'use strict'; + +import { Controller } from '@hotwired/stimulus'; + +enum Status { + OFFLINE = 'OFFLINE', + ONLINE = 'ONLINE', +} +export default class extends Controller { + static targets = ['message', 'attribute']; + static values = { + onlineMessage: { type: String, default: 'You are online.' }, + offlineMessage: { type: String, default: 'You are offline.' }, + }; + + declare readonly onlineMessageValue: string; + declare readonly offlineMessageValue: string; + declare readonly attributeTargets: HTMLElement[]; + declare readonly messageTargets: HTMLElement[]; + + connect() { + this.dispatchEvent('connect', {}); + if (navigator.onLine) { + this.statusChanged({ + status: Status.ONLINE, + message: this.onlineMessageValue, + }); + } else { + this.statusChanged({ + status: Status.OFFLINE, + message: this.offlineMessageValue, + }); + } + + window.addEventListener('offline', () => { + this.statusChanged({ + status: Status.OFFLINE, + message: this.offlineMessageValue, + }); + }); + window.addEventListener('online', () => { + this.statusChanged({ + status: Status.ONLINE, + message: this.onlineMessageValue, + }); + }); + } + dispatchEvent(name, payload) { + this.dispatch(name, { detail: payload, prefix: 'connection-status' }); + } + + statusChanged(data) { + this.messageTargets.forEach((element) => { + element.innerHTML = data.message; + }); + this.attributeTargets.forEach((element) => { + element.setAttribute('data-connection-status', data.status); + }); + this.dispatchEvent('status-changed', { detail: data }); + } +} diff --git a/assets/test/controller.test.ts b/assets/test/controller.test.ts new file mode 100644 index 0000000..750b982 --- /dev/null +++ b/assets/test/controller.test.ts @@ -0,0 +1,53 @@ +'use strict'; + +import {Application, Controller} from '@hotwired/stimulus'; +import {getByTestId, waitFor} from '@testing-library/dom'; +import {clearDOM, mountDOM} from '@symfony/stimulus-testing'; +import StatusController from '../src/controller'; + +// Controller used to check the actual controller was properly booted +class CheckController extends Controller { + connect() { + this.element.addEventListener('pwa-status:connect', () => { + this.element.classList.add('connected'); + }); + } +} + +const startStimulus = () => { + const application: Application = Application.start(); + application.register('check', CheckController); + application.register('pwa-status', StatusController); +}; + +describe('StatusController', () => { + let container: any; + + beforeEach(() => { + container = mountDOM(` + +
+