diff --git a/developer_manual/digging_deeper/index.rst b/developer_manual/digging_deeper/index.rst index 004f2b4b6..9d289d2d3 100644 --- a/developer_manual/digging_deeper/index.rst +++ b/developer_manual/digging_deeper/index.rst @@ -24,4 +24,5 @@ Digging deeper two-factor-provider users dashboard + projects web_host_metadata diff --git a/developer_manual/digging_deeper/projects.rst b/developer_manual/digging_deeper/projects.rst new file mode 100644 index 000000000..b47f16ef1 --- /dev/null +++ b/developer_manual/digging_deeper/projects.rst @@ -0,0 +1,216 @@ +======== +Projects +======== + +Projects are a way to link items from different apps via a common interface. + +App developers can integrate into projects and provide their own entity types to link with. + + +Register a resource provider +--------------------------- + +Things like files, deck cards and talk rooms are called Resources in projects. +In order to add your own resource type, we need to create a class implementing the +`OCP\Collaboration\Resources\IProvider` interface. + +.. code-block:: php + + url = $url; + } + + /** + * @inheritDoc + */ + public function getType(): string { + return self::RESOURCE_TYPE; + } + + /** + * @inheritDoc + */ + public function getResourceRichObject(IResource $resource): array { + $item = $this->getItem($resource); + $icon = $this->url->linkToRouteAbsolute('myapp.images.get_icon', ['id' => $item->getId()]); + $resourceUrl = $this->url->linkToRouteAbsolute('bookmarks.page.index', ['item' => $bookmark->getId()]); + + return [ + 'type' => self::RESOURCE_TYPE, + 'id' => $resource->getId(), + 'name' => $item->getTitle(), + 'link' => $resourceUrl, + 'iconUrl' => $favicon, + ]; + } + + /** + * @inheritDoc + */ + public function canAccessResource(IResource $resource, ?IUser $user): bool { + if ($resource->getType() !== self::RESOURCE_TYPE || !($user instanceof IUser)) { + return false; + } + $bookmark = $this->getItem($resource); + if ($bookmark === null) { + return false; + } + return $bookmark->getUserId() === $user->getUID() + } + + private function getItem(IResource $resource) : ?Item { + // implement me + } + } + + +The `MyResourceProvider` class needs to be registered during the :ref:`app bootstrap`. + +.. code-block:: php + + injectFn(Closure::fromCallable([$this, 'registerCollaborationResources'])); + } + + protected function registerCollaborationResources(IProviderManager $resourceManager, SymfonyAdapter $symfonyAdapter): void { + $resourceManager->registerResourceProvider(ResourceProvider::class); + + $symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () { + Util::addScript(self::APP_ID, 'collections'); + }); + } + } + +As you can see we also already register a front-end script, which we are going to create next. + + +Provide a user interface +------------------------ + +The user interface can be registered through the public `OCP.Collaboration.registerType` +JavaScript method. The first parameter represents the resource type that has already +been specified in the `IResourceProvider` implementation. The second parameter is an object with +three properties: + + * `typeString` A localized string that will be displayed in the dropdown when choosing which resource type to link to + * `typeIconClass` A CSS class of the icon that should be used for this entry + * `action` An async function that will produce a resource picker UI and resolves with the resource id + +The following example shows how a Vue.js component could be used to render the +widget user interface, however this approach works for any other framework as well +as plain JavaScript: + + +.. code-block:: javascript + + import Vue from 'vue' + import ItemPickerDialog from './components/ItemPickerDialog' + + OCP.Collaboration.registerType('myapp', { + action: () => { + return new Promise((resolve, reject) => { + const container = document.createElement('div') + container.id = 'myapp-item-select' + const body = document.getElementById('body-user') + body.appendChild(container) + const ComponentVM = new Vue({ + render: h => h(ItemPickerDialog), + }) + ComponentVM.$mount(container) + ComponentVM.$root.$on('close', () => { + ComponentVM.$el.remove() + ComponentVM.$destroy() + reject(new Error('User cancelled resource selection')) + }) + ComponentVM.$root.$on('select', (id) => { + resolve(id) + ComponentVM.$el.remove() + ComponentVM.$destroy() + }) + }) + }, + typeString: t('myapp', 'Link to an item'), + typeIconClass: 'icon-file', + }) + +This will allow other apps to link to your items. We also want to link to other apps' items. +Since all apps with projects support are listening on the above loadAdditionalScripts event, +we can simply dispatch it when we render our main page template. + +.. code-block:: php + + \OC::$server->getEventDispatcher()->dispatch('\OCP\Collaboration\Resources::loadAdditionalScripts'); + +In our Vue app, we can then render the pre-built projects picker available in the npm package `nextcloud-vue-collections`. + +.. code-block:: vue + + + +