Skip to content

feat: add mercure to vue.js template #313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
- run: yarn test-gen
- run: yarn test-next-app
- run: yarn test-react-app
- run: yarn test-vue-app
- run: yarn test-gen-env
- run: yarn test-gen-openapi3
- run: yarn check
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"test-gen-custom": "rm -rf ./tmp && yarn build && babel src/generators/ReactGenerator.js src/generators/BaseGenerator.js -d ./tmp/gens && cp -r ./templates/react ./templates/react-common ./templates/entrypoint.js ./tmp/gens && ./lib/index.js https://demo.api-platform.com ./tmp/react-custom -g \"$(pwd)/tmp/gens/ReactGenerator.js\" -t ./tmp/gens",
"test-gen-env": "rm -rf ./tmp && yarn build && API_PLATFORM_CLIENT_GENERATOR_ENTRYPOINT=https://demo.api-platform.com API_PLATFORM_CLIENT_GENERATOR_OUTPUT=./tmp ./lib/index.js",
"test-react-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create react-app ./tmp/app/reactapp && yarn --cwd ./tmp/app/reactapp add react-router-dom@5 redux redux-thunk react-redux redux-form connected-react-router && cp -R ./tmp/react/* ./tmp/app/reactapp/src && cp ./templates/react/index.js ./tmp/app/reactapp/src && start-server-and-test 'BROWSER=none yarn --cwd ./tmp/app/reactapp start' http://127.0.0.1:3000/books/ 'yarn playwright test'",
"test-next-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create next-app --typescript ./tmp/app/next && yarn --cwd ./tmp/app/next add isomorphic-unfetch formik react-query && cp -R ./tmp/next/* ./tmp/app/next && rm ./tmp/app/next/pages/index.tsx && rm -rf ./tmp/app/next/pages/api && yarn --cwd ./tmp/app/next build && start-server-and-test 'yarn --cwd ./tmp/app/next start' http://127.0.0.1:3000/books/ 'yarn playwright test'"
"test-next-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && yarn create next-app --typescript ./tmp/app/next && yarn --cwd ./tmp/app/next add isomorphic-unfetch formik react-query && cp -R ./tmp/next/* ./tmp/app/next && rm ./tmp/app/next/pages/index.tsx && rm -rf ./tmp/app/next/pages/api && yarn --cwd ./tmp/app/next build && start-server-and-test 'yarn --cwd ./tmp/app/next start' http://127.0.0.1:3000/books/ 'yarn playwright test'",
"test-vue-app": "rm -rf ./tmp/app && mkdir -p ./tmp/app && cd ./tmp/app && npm init -y vue@2 -- --router vue && cd ../.. && yarn --cwd ./tmp/app/vue add vuex@3 vuex-map-fields lodash && cp -R ./tmp/vue/* ./tmp/app/vue/src && cp ./templates/vue/main.js ./tmp/app/vue/src && yarn --cwd ./tmp/app/vue build && start-server-and-test 'yarn --cwd ./tmp/app/vue vite preview --host 127.0.0.1 --port 3000' http://127.0.0.1:3000/books/ 'yarn playwright test'"
},
"lint-staged": {
"src/**/*.js": "yarn lint --fix"
Expand Down
6 changes: 6 additions & 0 deletions src/generators/ReactGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export default class extends BaseGenerator {
constructor(params) {
super(params);

this.registerTemplates("common/", [
// utils
"utils/mercure.js",
]);

this.registerTemplates("react-common/", [
// actions
"actions/foo/create.js",
Expand Down Expand Up @@ -130,6 +135,7 @@ combineReducers({ ${titleLc},/* ... */ }),
context,
false
);
this.createFile("utils/mercure.js", `${dir}/utils/mercure.js`);

this.createEntrypoint(api.entrypoint, `${dir}/config/entrypoint.js`);
}
Expand Down
6 changes: 6 additions & 0 deletions src/generators/ReactNativeGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export default class extends BaseGenerator {
return options.inverse(this);
});

this.registerTemplates("common/", [
// utils
"utils/mercure.js",
]);

this.registerTemplates(`react-common/`, [
// actions
"actions/foo/create.js",
Expand Down Expand Up @@ -133,6 +138,7 @@ combineReducers({ ${titleLc}, /* ... */ }),

[
"utils/dataAccess.js",
"utils/mercure.js",
"utils/helpers.js",
"components/Spinner.js",
"components/Confirm.js",
Expand Down
24 changes: 21 additions & 3 deletions src/generators/VueGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ export default class extends BaseGenerator {
constructor(params) {
super(params);

this.registerTemplates("common/", [
// utils
"utils/mercure.js",
]);

this.registerTemplates(`vue/`, [
// modules
"store/modules/foo/index.js",
Expand Down Expand Up @@ -36,6 +41,10 @@ export default class extends BaseGenerator {
"components/foo/Update.vue",
"components/foo/Show.vue",

// mixins
"mixins/ItemWatcher.js",
"mixins/ListWatcher.js",

// routes
"router/foo.js",

Expand Down Expand Up @@ -102,9 +111,13 @@ export const store = new Vuex.Store({

// Create directories
// These directories may already exist
[`${dir}/config`, `${dir}/error`, `${dir}/router`, `${dir}/utils`].forEach(
(dir) => this.createDir(dir, false)
);
[
`${dir}/config`,
`${dir}/error`,
`${dir}/mixins`,
`${dir}/router`,
`${dir}/utils`,
].forEach((dir) => this.createDir(dir, false));

[
`${dir}/store/modules/${lc}`,
Expand Down Expand Up @@ -153,6 +166,10 @@ export const store = new Vuex.Store({
this.createFileFromPattern(pattern, dir, lc, context)
);

for (const file of ["mixins/ItemWatcher.js", "mixins/ListWatcher.js"]) {
this.createFile(file, `${dir}/${file}`);
}

// error
this.createFile(
"error/SubmissionError.js",
Expand All @@ -174,5 +191,6 @@ export const store = new Vuex.Store({
{ hydraPrefix: this.hydraPrefix },
false
);
this.createFile("utils/mercure.js", `${dir}/utils/mercure.js`);
}
}
23 changes: 23 additions & 0 deletions templates/common/utils/mercure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ENTRYPOINT } from "../config/entrypoint";

export const mercureSubscribe = (hubURL, topics, setData) => {
const url = new URL(hubURL, ENTRYPOINT);
topics.forEach(topic =>
url.searchParams.append("topic", (new URL(topic, ENTRYPOINT)).toString())
);
const eventSource = new EventSource(url.toString());
eventSource.addEventListener("message", (event) => setData(JSON.parse(event.data)));

return eventSource;
}

export const extractHubURL = (response) => {
const linkHeader = response.headers.get('Link');
if (!linkHeader) return null;

const matches = linkHeader.match(
/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/
);

return matches && matches[1] ? new URL(matches[1], ENTRYPOINT) : null;
}
14 changes: 4 additions & 10 deletions templates/react-common/actions/foo/list.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
fetch,
normalize,
extractHubURL,
mercureSubscribe as subscribe
} from '../../utils/dataAccess';
import { fetch, normalize } from '../../utils/dataAccess';
import { extractHubURL, mercureSubscribe as subscribe } from "../../utils/mercure";
import { success as deleteSuccess } from './delete';

export function error(error) {
Expand Down Expand Up @@ -61,11 +57,9 @@ export function reset(eventSource) {

export function mercureSubscribe(hubURL, topics) {
return dispatch => {
const eventSource = subscribe(hubURL, topics);
const eventSource = subscribe(hubURL, topics, data =>
dispatch(mercureMessage(normalize(data))));
dispatch(mercureOpen(eventSource));
eventSource.addEventListener('message', event =>
dispatch(mercureMessage(normalize(JSON.parse(event.data))))
);
};
}

Expand Down
14 changes: 4 additions & 10 deletions templates/react-common/actions/foo/show.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import {
fetch,
extractHubURL,
normalize,
mercureSubscribe as subscribe
} from '../../utils/dataAccess';
import { fetch, normalize } from '../../utils/dataAccess';
import { extractHubURL, mercureSubscribe as subscribe } from "../../utils/mercure";

export function error(error) {
return { type: '{{{uc}}}_SHOW_ERROR', error };
Expand Down Expand Up @@ -54,11 +50,9 @@ export function reset(eventSource) {

export function mercureSubscribe(hubURL, topic) {
return dispatch => {
const eventSource = subscribe(hubURL, [topic]);
const eventSource = subscribe(hubURL, [topic], data =>
dispatch(mercureMessage(normalize(data))));
dispatch(mercureOpen(eventSource));
eventSource.addEventListener('message', event =>
dispatch(mercureMessage(normalize(JSON.parse(event.data))))
);
};
}

Expand Down
14 changes: 4 additions & 10 deletions templates/react-common/actions/foo/update.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { SubmissionError } from 'redux-form';
import {
fetch,
extractHubURL,
normalize,
mercureSubscribe as subscribe
} from '../../utils/dataAccess';
import { fetch, normalize } from '../../utils/dataAccess';
import { extractHubURL, mercureSubscribe as subscribe } from "../../utils/mercure";
import { success as createSuccess } from './create';
import { loading, error } from './delete';

Expand Down Expand Up @@ -107,11 +103,9 @@ export function reset(eventSource) {

export function mercureSubscribe(hubURL, topic) {
return dispatch => {
const eventSource = subscribe(hubURL, [topic]);
const eventSource = subscribe(hubURL, [topic], data =>
dispatch(mercureMessage(normalize(data))));
dispatch(mercureOpen(eventSource));
eventSource.addEventListener('message', event =>
dispatch(mercureMessage(normalize(JSON.parse(event.data))))
);
};
}

Expand Down
19 changes: 0 additions & 19 deletions templates/react-common/utils/dataAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,6 @@ export function fetch(id, options = {}) {
});
}

export function mercureSubscribe(url, topics) {
topics.forEach(topic =>
url.searchParams.append('topic', new URL(topic, ENTRYPOINT))
);

return new EventSource(url.toString());
}

export function normalize(data) {
if (has(data, 'hydra:member')) {
// Normalize items in collections
Expand All @@ -70,14 +62,3 @@ export function normalize(data) {
: get(value, '@id', value)
);
}

export function extractHubURL(response) {
const linkHeader = response.headers.get('Link');
if (!linkHeader) return null;

const matches = linkHeader.match(
/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/
);

return matches && matches[1] ? new URL(matches[1], ENTRYPOINT) : null;
}
4 changes: 2 additions & 2 deletions templates/vue/components/foo/Create.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div>
<h1>New {{{title}}}</h1>
<h1>Create {{{title}}}</h1>

<div
v-if="isLoading"
Expand Down Expand Up @@ -29,7 +29,7 @@
<script>
import { createHelpers } from 'vuex-map-fields';
import { mapActions } from 'vuex';
import {{{titleUcFirst}}}Form from './Form';
import {{{titleUcFirst}}}Form from './Form.vue';

const { mapFields } = createHelpers({
getterType: '{{{lc}}}/create/getField',
Expand Down
14 changes: 13 additions & 1 deletion templates/vue/components/foo/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
<div
v-if="deletedItem"
class="alert alert-success">\{{ deletedItem['@id'] }} deleted.</div>
<div
v-if="mercureDeletedItem"
class="alert alert-success">\{{ mercureDeletedItem['@id'] }} deleted by another user.</div>
<div
v-if="error"
class="alert alert-danger">\{{ error }}</div>
Expand All @@ -21,7 +24,7 @@
<table class="table table-responsive table-striped table-hover">
<thead>
<tr>
<th>Id</th>
<th>id</th>
{{#each fields}}
<th>{{name}}</th>
{{/each }}
Expand Down Expand Up @@ -119,18 +122,27 @@
<script>
import { mapActions } from 'vuex';
import { mapFields } from 'vuex-map-fields';
import ListWatcher from '../../mixins/ListWatcher';
import * as types from '../../store/modules/{{{lc}}}/list/mutation_types'
import * as delTypes from '../../store/modules/{{{lc}}}/delete/mutation_types';

export default {
mixins: [ListWatcher],
computed: {
...mapFields('{{{lc}}}/del', {
deletedItem: 'deleted',
mercureDeletedItem: 'mercureDeleted',
}),
...mapFields('{{{lc}}}/list', {
error: 'error',
items: 'items',
hubUrl: 'hubUrl',
isLoading: 'isLoading',
view: 'view',
}),
itemUpdateMutation: () => `{{{lc}}}/list/${types.UPDATE_ITEM}`,
itemDeleteMutation: () => `{{{lc}}}/list/${types.DELETE_ITEM}`,
itemMercureDeletedMutation: () => `{{{lc}}}/del/${delTypes.{{{uc}}}_DELETE_MERCURE_SET_DELETED}`,
},

mounted() {
Expand Down
40 changes: 34 additions & 6 deletions templates/vue/components/foo/Show.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div>
<h1>Show \{{ item && item['@id'] }}</h1>
<h1>Show Book \{{ item && item['@id'] }}</h1>

<div
v-if="isLoading"
Expand Down Expand Up @@ -56,24 +56,52 @@
</router-link>
<button
class="btn btn-danger"
@click="deleteItem(item)">Delete</button>
@click="del">Delete</button>
</div>
</template>

<script>
import { mapActions } from 'vuex';
import { mapFields } from 'vuex-map-fields';
import ItemWatcher from '../../mixins/ItemWatcher';
import * as types from '../../store/modules/{{{lc}}}/show/mutation_types';
import * as delTypes from '../../store/modules/{{{lc}}}/delete/mutation_types';

export default {
mixins: [ItemWatcher],
computed: {
...mapFields('{{{lc}}}/del', {
deleteError: 'error',
deleted: 'deleted',
mercureDeleted: 'mercureDeleted',
}),
...mapFields('{{{lc}}}/show', {
error: 'error',
isLoading: 'isLoading',
item: 'retrieved',
hubUrl: 'hubUrl',
}),
itemUpdateMutation: () =>`{{{lc}}}/show/${types.{{{uc}}}_SHOW_SET_RETRIEVED}`,
itemMercureDeletedMutation: () => `{{{lc}}}/del/${delTypes.{{{uc}}}_DELETE_MERCURE_SET_DELETED}`,
},

watch: {
// eslint-disable-next-line object-shorthand,func-names
deleted: function(deleted) {
if (!deleted) {
return;
}

this.$router.push({ name: '{{{titleUcFirst}}}List' });
},
// eslint-disable-next-line object-shorthand,func-names
mercureDeleted: function(deleted) {
if (!deleted) {
return;
}

this.$router.push({ name: '{{{titleUcFirst}}}List' });
},
},

beforeDestroy () {
Expand All @@ -86,14 +114,14 @@ export default {

methods: {
...mapActions({
del: '{{{lc}}}/del/del',
deleteItem: '{{{lc}}}/del/del',
reset: '{{{lc}}}/show/reset',
retrieve: '{{{lc}}}/show/retrieve',
}),

deleteItem (item) {
if (window.confirm('Are you sure you want to delete this item?')) {
this.del(item).then(() => this.$router.push({ name: '{{{titleUcFirst}}}List' }));
del() {
if (window.confirm('Are you sure you want to delete this {{{lc}}}?')) {
this.deleteItem(this.item);
}
},
},
Expand Down
Loading