Compare commits

..

No commits in common. "master" and "next" have entirely different histories.
master ... next

30 changed files with 1631 additions and 3774 deletions

View file

@ -1,23 +1,3 @@
# Blue Conf 2019
The code for my Blue Conf 2019 talk - [Decoupling Drupal with Vue.js](https://www.oliverdavies.uk/talks/decoupling-drupal-vuejs).
## Installation
1. Clone the repository
```
git clone https://github.com/opdavies/blue-conf-2019
cd blue-conf-2019
```
1. Follow the instructions for [setting up the Drupal back-end][drupal].
1. Follow the instructions for [setting up the Vue.js front-end][vuejs].
## Author
[Oliver Davies](https://www.oliverdavies.uk)
[drupal]: /docs/setting-up-drupal.md
[vuejs]: /docs/setting-up-vue-js.md
TODO

View file

@ -1,22 +1,3 @@
# Setting up the Back-End Drupal Application
The Drupal back-end for my Blue Conf 2019 talk - [Decoupling Drupal with Vue.js](https://www.oliverdavies.uk/talks/decoupling-drupal-vuejs). This was used alongside the Vue.js front-end application for viewing and submitting fictional talk proposals that were stored in Drupal.
## Prerequisites
* [Docksal](https://docksal.io)
* [VirtualBox](https://www.virtualbox.org/wiki/Downloads) (can be changed to use Docker natively)
## Setup instructions
1. Run `fin init` to initialise the project, including installing all of the Composer dependencies, installing Drupal and importing the original configuration and creating some test content.
```bash
cd drupal
fin init
```
1. Visit `http://blueconf.docksal` to view the Drupal website, or run `fin drush status` to ensure that everything is running.
1. Run `fin drush uli` to generate a one-time login link in order to access the site.
TODO

View file

@ -1,28 +1,2 @@
# Setting up the Front-End Vue.js Application
The Vue.js front-end for my Blue Conf 2019 talk - [Decoupling Drupal with Vue.js](https://www.oliverdavies.uk/talks/decoupling-drupal-vuejs). It is a [Vue CLI](https://cli.vuejs.org) application, and uses [Tailwind CSS](https://tailwindcss.com) for styling.
## Prerequisites
* [npm](https://docs.npmjs.com/cli/npm)
* [yarn](https://yarnpkg.com) (optional)
## Setup instructions
1. Install the npm dependencies using either `npm` or `yarn`.
```bash
cd vuejs
# Using npm
npm install
# Using yarn
yarn
```
1. Change the URL to the Drupal back-end if needed in `.env`.
1. Use `yarn serve` to start a local web server.
1. Visit the URL (usually `http://localhost:8080`) to view the front-end application.
TODO

View file

@ -1,37 +1,34 @@
#!/usr/bin/env bash
## Initialize stack and site (full reset)
## Initialise the project
##
## Usage: fin init
# Abort if anything fails
set -e
fin start
#-------------------------- Helper functions --------------------------------
fin run composer install
# Console colors
red='\033[0;31m'
green='\033[0;32m'
green_bg='\033[1;97;42m'
yellow='\033[1;33m'
NC='\033[0m'
fin drush site:install -y
echo-red () { echo -e "${red}$1${NC}"; }
echo-green () { echo -e "${green}$1${NC}"; }
echo-green-bg () { echo -e "${green_bg}$1${NC}"; }
echo-yellow () { echo -e "${yellow}$1${NC}"; }
# Reset the site uuid.
fin drush config:set -y system.site uuid de7ba5dc-5795-4cb5-9d38-1edcc27be491
#-------------------------- Execution --------------------------------
# Delete the uuid for shortcut.set.default so that config will import.
fin drush config:delete -y shortcut.set.default uuid
# Stack initialization
echo -e "${green_bg} Step 1 ${NC}${green} Initializing stack...${NC}"
fin project reset -f
# Import config.
fin drush config:import -y --source=../config/sync
# Site initialization
echo -e "${green_bg} Step 2 ${NC}${green} Initializing site...${NC}"
# This runs inside cli using http://docs.docksal.io/en/v1.4.0/fin/custom-commands/#executing-commands-inside-cli
fin init-site
fin drush php:eval '\Drupal::service("Drupal\dtc_import\Service\Importer\CsvSpeakerImporter")->import()'
fin drush php:eval '\Drupal::service("Drupal\dtc_import\Service\Importer\CsvSessionImporter")->import()'
echo -e "${green_bg} DONE! ${NC}${green} Completed all initialization steps.${NC}"
fin drush user:create api --password=api
fin drush user:role:add api_user api
#-------------------------- END: Execution --------------------------------
# Set uuid for admin user.
fin drush sql:query "UPDATE users SET uuid = '11dad4c2-baa8-4fb2-97c6-12e1ce925806' WHERE uid = 1"
# Set uuid for API user.
fin drush sql:query "UPDATE users SET uuid = '63936126-87cd-4166-9cb4-63b61a210632' WHERE uid = 7"
fin uli

View file

@ -1,85 +0,0 @@
#!/usr/bin/env bash
#: exec_target = cli
## Initialize/reinstall site
##
## Usage: fin init-site
# Abort if anything fails
set -e
#-------------------------- Helper functions --------------------------------
copy_settings_file() {
local source="$1"
local dest="$2"
echo "Copying ${dest}..."
cp $source $dest
}
composer_install() {
echo "Installing Composer dependencies..."
composer install
}
#-------------------------- END: Helper functions --------------------------------
#-------------------------- END: Functions --------------------------------
init_settings() {
copy_settings_file ../files/settings.php ../../web/sites/default
}
site_install() {
composer_install
echo "Installing Drupal..."
drush site:install -y
}
import_config() {
drush config:set -y system.site uuid de7ba5dc-5795-4cb5-9d38-1edcc27be491
drush config:delete -y shortcut.set.default uuid
echo "Importing configuration..."
drush config:import -y --source=../config/sync
}
import_content() {
echo "Importing speakers from CSV..."
drush php:eval '\Drupal::service("Drupal\dtc_import\Service\Importer\CsvSpeakerImporter")->import()'
echo "Importing sessions from CSV..."
drush php:eval '\Drupal::service("Drupal\dtc_import\Service\Importer\CsvSessionImporter")->import()'
}
setup_users() {
echo "Creating the API user..."
drush user:create api --password=api
drush user:role:add api_user api
echo "Resetting uuid for the admin user..."
drush sql:query "UPDATE users SET uuid = '11dad4c2-baa8-4fb2-97c6-12e1ce925806' WHERE uid = 1"
echo "Resetting uuid for the API user..."
drush sql:query "UPDATE users SET uuid = '63936126-87cd-4166-9cb4-63b61a210632' WHERE uid = 7"
echo "Rebuilding cache..."
drush cache:rebuild
}
#-------------------------- END: Functions --------------------------------
#-------------------------- Execution --------------------------------
site_install
import_config
import_content
setup_users
echo -e "Open ${yellow}http://${VIRTUAL_HOST}${NC} in your browser to verify the setup."
#-------------------------- END: Execution --------------------------------

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,7 @@
"drupal/core": "^8.7.0",
"drush/drush": "^9.0.0",
"josephlavin/tap": "^1.0",
"tightenco/collect": "^6.2",
"tightenco/collect": "^5.8",
"vlucas/phpdotenv": "^2.4",
"webflo/drupal-finder": "^1.0.0",
"webmozart/path-util": "^2.3",

586
drupal/composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
module:
admin_toolbar: 0
admin_toolbar_tools: 0
automated_cron: 0
basic_auth: 0
big_pipe: 0

View file

@ -19,9 +19,6 @@ settings:
-
value: accepted
label: Accepted
-
value: rejected
label: Rejected
allowed_values_function: ''
module: options
locked: false

View file

@ -1,11 +0,0 @@
uuid: 6628c7ca-bb05-487d-8953-1f935f424ccf
langcode: en
status: true
dependencies:
module:
- node
id: dtc_sessions_accept_session
label: 'Accept session'
type: node
plugin: dtc_sessions_accept_session
configuration: { }

View file

@ -1,11 +0,0 @@
uuid: 8e092524-f8ca-45f1-b093-bae311fa3694
langcode: en
status: true
dependencies:
module:
- node
id: dtc_sessions_reject_session
label: 'Reject session'
type: node
plugin: dtc_sessions_reject_session
configuration: { }

View file

@ -1,5 +1,5 @@
uuid: de7ba5dc-5795-4cb5-9d38-1edcc27be491
name: 'Blue Conf'
name: 'BlueConf'
mail: admin@example.com
slogan: ''
page:

View file

@ -2,8 +2,6 @@ uuid: f320f9b4-51c4-4a06-8289-0c5b2c1e46c5
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_session_status
module:
- node
- options
@ -65,9 +63,11 @@ display:
type: type
name: name
status: status
field_session_status: field_session_status
changed: changed
operations: operations
edit_node: edit_node
delete_node: delete_node
dropbutton: dropbutton
timestamp: title
info:
node_bulk_form:
align: ''
@ -102,13 +102,6 @@ display:
separator: ''
empty_column: false
responsive: ''
field_session_status:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
changed:
sortable: true
default_sort_order: desc
@ -116,7 +109,30 @@ display:
separator: ''
empty_column: false
responsive: priority-low
operations:
edit_node:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
delete_node:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
dropbutton:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
timestamp:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
@ -268,68 +284,6 @@ display:
plugin_id: field
entity_type: node
entity_field: status
field_session_status:
id: field_session_status
table: node__field_session_status
field: field_session_status
relationship: none
group_type: group
admin_label: ''
label: 'Session status'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: list_default
settings: { }
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
changed:
id: changed
table: node_field_data
@ -660,8 +614,7 @@ display:
- 'user.node_grants:view'
- user.permissions
max-age: 0
tags:
- 'config:field.storage.node.field_session_status'
tags: { }
page_1:
display_options:
path: admin/content/node
@ -693,5 +646,4 @@ display:
- 'user.node_grants:view'
- user.permissions
max-age: 0
tags:
- 'config:field.storage.node.field_session_status'
tags: { }

View file

@ -1,10 +0,0 @@
langcode: en
status: true
dependencies:
module:
- node
id: dtc_sessions_accept_session
label: 'Accept session'
type: node
plugin: dtc_sessions_accept_session
configuration: { }

View file

@ -1,10 +0,0 @@
langcode: en
status: true
dependencies:
module:
- node
id: dtc_sessions_reject_session
label: 'Reject session'
type: node
plugin: dtc_sessions_reject_session
configuration: { }

View file

@ -1,25 +0,0 @@
<?php
namespace Drupal\dtc_sessions\Plugin\Action;
use Drupal\Core\Field\FieldUpdateActionBase;
/**
* Accepts a session.
*
* @Action(
* id = "dtc_sessions_accept_session",
* label = @Translation("Accept session"),
* type = "node"
* )
*/
class AcceptSession extends FieldUpdateActionBase {
/**
* {@inheritdoc}
*/
protected function getFieldsToUpdate() {
return ['field_session_status' => 'accepted'];
}
}

View file

@ -1,25 +0,0 @@
<?php
namespace Drupal\dtc_sessions\Plugin\Action;
use Drupal\Core\Field\FieldUpdateActionBase;
/**
* Rejects a session.
*
* @Action(
* id = "dtc_sessions_reject_session",
* label = @Translation("Reject session"),
* type = "node"
* )
*/
class RejectSession extends FieldUpdateActionBase {
/**
* {@inheritdoc}
*/
protected function getFieldsToUpdate() {
return ['field_session_status' => 'rejected'];
}
}

View file

@ -7,5 +7,3 @@ $databases['default']['default'] = [
'username' => getenv('MYSQL_USER'),
'password' => getenv('MYSQL_PASSWORD'),
];
$settings['container_yamls'][] = __DIR__ . '/services.docksal.yml';

View file

@ -9,29 +9,25 @@
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"@fullhuman/postcss-purgecss": "^1.3.0",
"axios": "^0.19.0",
"axios": "^0.18.0",
"core-js": "^2.6.5",
"postcss-import": "^12.0.1",
"postcss-nested": "^4.1.2",
"qs": "^6.9.0",
"tailwindcss": "^1.1.2",
"tailwindcss": "^1.0.1",
"vue": "^2.6.10",
"vue-router": "^3.1.3",
"vue-save-state": "^1.2.0"
"vue-router": "^3.0.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.12.0",
"@vue/cli-plugin-eslint": "^3.12.0",
"@vue/cli-plugin-unit-jest": "^3.12.0",
"@vue/cli-service": "^3.12.0",
"@vue/cli-plugin-babel": "^3.7.0",
"@vue/cli-plugin-eslint": "^3.7.0",
"@vue/cli-plugin-unit-jest": "^3.7.0",
"@vue/cli-service": "^3.7.0",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.3",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.6.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.2.3",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"
}
}

View file

@ -1,15 +1,7 @@
module.exports = {
plugins: [
require('postcss-import'),
require('tailwindcss'),
require('postcss-nested'),
require('autoprefixer'),
process.env.NODE_ENV === 'production' && require('@fullhuman/postcss-purgecss')({
content: [
'./src/**/*.vue',
'./public/index.html',
],
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
})
]
plugins: {
tailwindcss: {},
'postcss-nested': {},
autoprefixer: {}
}
}

View file

@ -1,22 +1,20 @@
<template>
<div id="app" class="antialiased min-h-screen font-sans bg-gray-100 text-black p-12">
<div class="w-full max-w-2xl mx-auto">
<AcceptedSessionsList :sessions="sessions"/>
<SessionForm/>
<accepted-sessions-list :sessions="sortedSessions" />
<session-form @submitted="addSession($event)"></session-form>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import AcceptedSessionsList from '@/components/AcceptedSessionsList'
import axios from 'axios'
import qs from 'qs'
import saveState from 'vue-save-state'
import SessionForm from '@/components/SessionForm'
export default {
mixins: [saveState],
const axios = require('axios')
export default {
components: {
AcceptedSessionsList,
SessionForm
@ -32,12 +30,7 @@ export default {
mounted () {
const baseUrl = process.env.VUE_APP_DRUPAL_URL
const params = qs.stringify({
'fields[node--session]': 'title',
'filter[field_session_status]': 'accepted'
})
axios.get(`${baseUrl}/jsonapi/node/session?${params}`)
axios.get(`${baseUrl}/jsonapi/node/session`)
.then(({ data }) => {
this.loaded = true
this.sessions = data.data
@ -45,13 +38,41 @@ export default {
},
methods: {
getSaveStateConfig () {
return {
cacheKey: 'app'
}
addSession: function (session) {
this.sessions.push(session)
}
},
computed: {
sortedSessions: function () {
return _(this.sessions).sortBy(({ attributes }) => attributes.title)
}
}
}
</script>
<style src="./assets/css/tailwind.css"></style>
<style>
@tailwind base;
h1,
h2 {
@apply font-semibold
}
input,
textarea {
@apply w-full border border-gray-400 p-2 mt-1
}
input[type=submit] {
@apply w-full;
@screen sm {
@apply w-auto
}
}
@tailwind components;
@tailwind utilities;
</style>

View file

@ -1,17 +0,0 @@
h1,
h2 {
@apply font-semibold
}
input,
textarea {
@apply mt-1 p-2 w-full border border-gray-400
}
input[type=submit] {
@apply w-full;
@screen sm {
@apply w-auto
}
}

View file

@ -1,6 +0,0 @@
@import 'tailwindcss/base';
@import './base.css';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

View file

@ -1,10 +1,10 @@
<template>
<div>
<h1 class="text-4xl">Accepted Sessions</h1>
<h1 class="text-4xl mb-2">Sessions</h1>
<div v-if="sessions.length" class="mt-2 p-6 bg-white rounded-lg border">
<ul>
<li v-for="{ attributes } in sortedSessions" :key="attributes.drupal_internal__nid" class="mt-3 first:mt-0">
<div v-if="acceptedSessions.length" class="bg-white p-6 rounded-lg border">
<ul class="-mb-3">
<li v-for="{ attributes } in acceptedSessions" :key="attributes.drupal_internal__nid" class="mb-3">
{{ attributes.title }}
</li>
</ul>
@ -13,19 +13,25 @@
</template>
<script>
import sortBy from 'lodash/sortBy'
export default {
props: {
sessions: {
type: Array,
type: Object,
required: true
}
},
computed: {
sortedSessions: function () {
return sortBy(this.sessions, 'attributes.title')
acceptedSessions: function () {
return this.sessions
.filter(session => this.isAccepted(session))
.value()
}
},
methods: {
isAccepted: function ({ attributes }) {
return attributes.field_session_status === 'accepted'
}
}
}

View file

@ -2,8 +2,9 @@
<section class="mt-8">
<h2 class="text-2xl mb-4">Submit a Session</h2>
<SessionFormMessage :messages="messages" class="bg-green-100 border-green-300"/>
<SessionFormMessage :messages="errors" class="bg-red-100 border-red-300"/>
<session-form-message :messages="messages" class="bg-green-100 border-green-300"></session-form-message>
<session-form-message :messages="errors" class="bg-red-100 border-red-300"></session-form-message>
<form action="" @submit.prevent="submit">
<label class="block mb-4">
@ -23,7 +24,7 @@
<script>
import axios from 'axios'
import map from 'lodash/map'
import _ from 'lodash'
import SessionFormMessage from '@/components/SessionFormMessage'
export default {
@ -36,7 +37,7 @@ export default {
errors: [],
form: {
body: '',
field_session_status: 'submitted',
field_session_status: 'accepted',
field_session_type: 'full',
title: ''
},
@ -79,17 +80,19 @@ export default {
'Authorization': 'Basic YXBpOmFwaQ==',
'Content-Type': 'application/vnd.api+json'
}
}).then(({ data }) => {
this.errors = []
this.messages = []
const title = data.data.attributes.title
}).then(({ data: { data: { attributes } } }) => {
const title = attributes.title
this.messages.push(`Session ${title} has been created.`)
this.$emit('submitted', data)
this.form.body = ''
this.form.title = ''
this.errors = []
this.messages = []
}).catch(({ response: { data } }) => {
this.errors = map(data.errors, 'detail')
this.errors = _(data.errors).map('detail').value()
})
}
}

View file

@ -1,6 +1,6 @@
<template>
<div v-if="messages.length" class="mb-6 p-4 border">
<ul class="ml-3 list-disc list-inside">
<div v-if="messages.length" class="border p-4 mb-6">
<ul class="list-disc list-inside ml-3">
<li v-for="message in messages" :key="message">{{ message }}</li>
</ul>
</div>

View file

@ -1,9 +0,0 @@
module.exports = {
theme: {
extend: {}
},
variants: {
margin: ['responsive', 'first', 'last'],
},
plugins: []
}

File diff suppressed because it is too large Load diff