Skip to content

Commit

Permalink
Merge pull request #2795 from nextcloud/enh/talk-action
Browse files Browse the repository at this point in the history
Register talk message action for creating deck cards
  • Loading branch information
juliusknorr authored Feb 19, 2021
2 parents 8aa6782 + b8aed5f commit 4debfbd
Show file tree
Hide file tree
Showing 7 changed files with 372 additions and 50 deletions.
4 changes: 4 additions & 0 deletions lib/AppInfo/Application20.php
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ protected function registerCollaborationResources(IProviderManager $resourceMana
$resourceManager->registerResourceProvider(ResourceProviderCard::class);

$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
if (strpos(\OC::$server->getRequest()->getPathInfo(), '/call/') === 0) {
// Talk integration has its own entrypoint which already includes collections handling
return;
}
Util::addScript('deck', 'collections');
});
}
Expand Down
4 changes: 4 additions & 0 deletions lib/Listeners/BeforeTemplateRenderedListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,9 @@ public function handle(Event $event): void {
if (strpos($this->request->getPathInfo(), '/apps/calendar') === 0) {
Util::addScript('deck', 'calendar');
}

if (strpos($this->request->getPathInfo(), '/call/') === 0) {
Util::addScript('deck', 'talk');
}
}
}
247 changes: 247 additions & 0 deletions src/CardCreateDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
<!--
- @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
-
- @author Julius Härtl <jus@bitgrid.net>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<Modal class="card-selector" @close="close">
<div class="modal-scroller">
<div v-if="!creating && !created" id="modal-inner" :class="{ 'icon-loading': loading }">
<h3>{{ t('deck', 'Create a new card') }}</h3>
<Multiselect v-model="selectedBoard"
:placeholder="t('deck', 'Select a board')"
:options="boards"
:disabled="loading"
label="title"
class="multiselect-board"
@select="fetchCardsFromBoard">
<template slot="singleLabel" slot-scope="props">
<span>
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
<span>{{ props.option.title }}</span>
</span>
</template>
<template slot="option" slot-scope="props">
<span>
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
<span>{{ props.option.title }}</span>
</span>
</template>
</Multiselect>

<Multiselect v-model="selectedStack"
:placeholder="t('deck', 'Select a list')"
:options="stacksFromBoard"
:max-height="100"
:disabled="loading || !selectedBoard"
class="multiselect-list"
label="title" />

<input v-model="pendingTitle"
type="text"
:placeholder="t('deck', 'Card title')"
:disabled="loading || !selectedStack">
<textarea v-model="description" :disabled="loading || !selectedStack" />
<div class="modal-buttons">
<button @click="close">
{{ t('deck', 'Cancel') }}
</button>
<button :disabled="loading || !isBoardAndStackChoosen"
class="primary"
@click="select">
{{ action }}
</button>
</div>
</div>
<div v-else id="modal-inner">
<EmptyContent v-if="creating" icon="icon-loading">
{{ t('deck', 'Creating the new card…') }}
</EmptyContent>
<EmptyContent v-else-if="created" icon="icon-checkmark">
{{ t('deck', '"{card}" was added to "{board}"', { card: pendingTitle, board: selectedBoard.title }) }}
<template #desc>
<button class="primary">
{{ t('deck', 'Open card') }}
</button>
<button @click="close">
{{ t('deck', 'Close') }}
</button>
</template>
</EmptyContent>
</div>
</div>
</Modal>
</template>

<script>
import { generateUrl } from '@nextcloud/router'
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent'
import axios from '@nextcloud/axios'
import { CardApi } from './services/CardApi'
const cardApi = new CardApi()
export default {
name: 'CardCreateDialog',
components: {
EmptyContent,
Modal,
Multiselect,
},
props: {
title: {
type: String,
default: '',
},
description: {
type: String,
default: '',
},
action: {
type: String,
default: t('deck', 'Create card'),
},
},
data() {
return {
boards: [],
stacksFromBoard: [],
loading: true,
pendingTitle: '',
pendingDescription: '',
selectedStack: '',
selectedBoard: '',
creating: false,
created: false,
}
},
computed: {
isBoardAndStackChoosen() {
return !(this.selectedBoard === '')
},
},
beforeMount() {
this.fetchBoards()
},
mounted() {
this.pendingTitle = this.title
this.pendingDescription = this.description
},
methods: {
fetchBoards() {
axios.get(generateUrl('/apps/deck/boards')).then((response) => {
this.boards = response.data
this.loading = false
})
},
async fetchCardsFromBoard(board) {
try {
this.cardsFromBoard = []
const url = generateUrl('/apps/deck/stacks/' + board.id)
const response = await axios.get(url)
response.data.forEach(stack => {
this.stacksFromBoard.push(stack)
})
} catch (err) {
return err
}
},
close() {
this.$root.$emit('close')
},
async select() {
this.creating = true
await cardApi.addCard({
boardId: this.selectedBoard.id,
stackId: this.selectedStack.id,
title: this.pendingTitle,
description: this.pendingDescription,
})
this.creating = false
this.created = true
// We do not emit here since we want to give feedback to the user that the card was created
// this.$root.$emit('select', createdCard)
},
},
}
</script>

<style lang="scss" scoped>
.modal-scroller {
overflow: scroll;
max-height: calc(80vh - 40px);
margin: 10px;
}
#modal-inner {
width: 90vw;
max-width: 400px;
padding: 10px;
min-height: 200px;
}
.multiselect-board, .multiselect-list, input, textarea {
width: 100%;
margin-bottom: 10px !important;
}
ul {
min-height: 100px;
}
li {
padding: 6px;
border: 1px solid transparent;
}
li:hover, li:focus {
background-color: var(--color-background-dark);
}
.board-bullet {
display: inline-block;
width: 12px;
height: 12px;
border: none;
border-radius: 50%;
cursor: pointer;
}
.modal-buttons {
display: flex;
justify-content: flex-end;
}
.card-selector::v-deep .modal-container {
overflow: visible !important;
}
.empty-content {
margin-top: 5vh !important;
&::v-deep h2 {
margin-bottom: 5vh;
}
}
</style>
47 changes: 47 additions & 0 deletions src/helpers/selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Vue from 'vue'

const buildSelector = (selector, propsData = {}) => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
document.getElementById('body-user').append(container)
const View = Vue.extend(selector)
const ComponentVM = new View({
propsData,
}).$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
resolve(id)
})
})
}

export {
buildSelector,
}
57 changes: 7 additions & 50 deletions src/init-collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import BoardSelector from './BoardSelector'
import CardSelector from './CardSelector'
import './../css/collections.css'
import FileSharingPicker from './views/FileSharingPicker'
import { buildSelector } from './helpers/selector'

// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken);
// eslint-disable-next-line
Expand All @@ -41,61 +43,16 @@ window.addEventListener('DOMContentLoaded', () => {
} else {
console.error('OCA.Sharing.ShareSearch not ready')
}
});

((function(OCP) {

OCP.Collaboration.registerType('deck', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'deck-board-select'
const body = document.getElementById('body-user')
body.append(container)
const ComponentVM = new Vue({
render: h => h(BoardSelector),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Board selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
})
},
window.OCP.Collaboration.registerType('deck', {
action: () => buildSelector(BoardSelector),
typeString: t('deck', 'Link to a board'),
typeIconClass: 'icon-deck',
})

OCP.Collaboration.registerType('deck-card', {
action: () => {
return new Promise((resolve, reject) => {
const container = document.createElement('div')
container.id = 'deck-board-select'
const body = document.getElementById('body-user')
body.append(container)
const ComponentVM = new Vue({
render: h => h(CardSelector),
})
ComponentVM.$mount(container)
ComponentVM.$root.$on('close', () => {
ComponentVM.$el.remove()
ComponentVM.$destroy()
reject(new Error('Card selection canceled'))
})
ComponentVM.$root.$on('select', (id) => {
resolve(id)
ComponentVM.$el.remove()
ComponentVM.$destroy()
})
})
},
window.OCP.Collaboration.registerType('deck-card', {
action: () => buildSelector(CardSelector),
typeString: t('deck', 'Link to a card'),
typeIconClass: 'icon-deck',
})
})(window.OCP))
})
Loading

0 comments on commit 4debfbd

Please sign in to comment.