Skip to content

Commit

Permalink
feat: add mercure to vue.js template (#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shywim authored Aug 25, 2022
1 parent b9555f7 commit 26fba58
Show file tree
Hide file tree
Showing 35 changed files with 348 additions and 83 deletions.
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

0 comments on commit 26fba58

Please sign in to comment.