How can I build a Vue.js 3 component that displays icons for all installed & enabled Nextcloud apps

Hello, I set myself a technical challenge. The application icons show up in Nextcloud’s top menu, okay. But I’d like to create an Application with Vue.js 3 component that displays the icons of all the applications installed and enabled on my Nextcloud instance. How does that work? Do I need to fetch a specific API? Do I need to retrieve the information from the database? I’m a beginner in programming and I’m trying to understand how Nextcloud works. Thank you in advance for being pedagogical with me, given my beginner level.

Hello @bigpoppa,

you posted multiple questions, I try to answer them one-by-one. First, there was the question “how does this work”.

NC (server) provides the outer shell for any frontend you see. Part of that is the headline (with the various apps visible) and some more stuff (which is partially invisible). Upon loading the page, the browser detects the small Vue component in the header area and loads it. To get the information into the app, the server provides a so-called initial state. This is a (hidden) input field as part of the HTML code (look for initial-state-core-apps). This one has a value (base64-encoded) holding a JSON array with all installed apps.

On my instance for example are these the initial state for the header app.
[
  {
    "id": "spreed",
    "name": "Talk",
    "href": "https://cloud.example.de/apps/spreed/",
    "icon": "/custom_apps/spreed/img/app.svg",
    "order": -5,
    "type": "link",
    "active": false,
    "unread": 0,
    "classes": "",
    "app": "spreed",
    "default": true
  },
  {
    "id": "files",
    "order": 0,
    "href": "/apps/files/",
    "icon": "/apps/files/img/app.svg",
    "type": "link",
    "name": "Dateien",
    "app": "files",
    "active": true,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "activity",
    "order": 1,
    "href": "/apps/activity/",
    "icon": "/apps/activity/img/activity.svg",
    "type": "link",
    "name": "Aktivit�t",
    "app": "activity",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "mail",
    "order": 3,
    "href": "/apps/mail/",
    "icon": "/custom_apps/mail/img/mail.svg",
    "type": "link",
    "name": "E-Mail",
    "app": "mail",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "contacts",
    "order": 4,
    "href": "/apps/contacts/",
    "icon": "/custom_apps/contacts/img/app.svg",
    "type": "link",
    "name": "Kontakte",
    "app": "contacts",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "calendar",
    "order": 5,
    "href": "/apps/calendar/",
    "icon": "/custom_apps/calendar/img/calendar.svg",
    "type": "link",
    "name": "Kalender",
    "app": "calendar",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "notes",
    "order": 10,
    "href": "/apps/notes/",
    "icon": "/custom_apps/notes/img/notes.svg",
    "type": "link",
    "name": "Notizen",
    "app": "notes",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "collectives",
    "order": 12,
    "href": "/apps/collectives/",
    "icon": "/custom_apps/collectives/img/collectives.svg",
    "type": "link",
    "name": "Kollektive",
    "app": "collectives",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "polls",
    "order": 77,
    "href": "/apps/polls/",
    "icon": "/custom_apps/polls/img/app.svg",
    "type": "link",
    "name": "Umfragen",
    "app": "polls",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "forms",
    "order": 77,
    "href": "/apps/forms/",
    "icon": "/custom_apps/forms/img/forms.svg",
    "type": "link",
    "name": "Formulare",
    "app": "forms",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "tasks",
    "order": 100,
    "href": "/apps/tasks/",
    "icon": "/custom_apps/tasks/img/tasks.svg",
    "type": "link",
    "name": "Aufgaben",
    "app": "tasks",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  },
  {
    "id": "tables",
    "order": 100,
    "href": "/apps/tables/",
    "icon": "/custom_apps/tables/img/app.svg",
    "type": "link",
    "name": "Tabellen",
    "app": "tables",
    "active": false,
    "unread": 0,
    "classes": "",
    "default": false
  }
]

The Vue component will then read this value and display the app list accordingly.

Now, you want to create an app that shows the installed apps of the NC instance. OK, but just let me point out one detail: Really all apps? NC offers to restrict the usage of apps to a certain set of users/groups. The header will hide these if you do not have access to them. Just to think about it and your use case.

Now, depending on the exact use-case, you can do different things.

  1. Hook purely on the frontend into the initial state info from the core: This is a really bad idea. Do not do it. As soon as the core changes anything in the transport scheme, your app ceases to work. This can happen anytime as is is considered internal structure.
  2. Hook into the initial state using a library You can use the library @ nextcloud/initial-state. This is slightly better as you use the official way to access the initial state information. Nevertheless, the structure or naming might change anytime. So, this is brittle as well.
  3. Ask in the backend and create your own REST endpoints/initial state The server indeed does know the list of (enabled) apps. You could write a small backend part of your app that queries this information and provides the information to the frontend. The frontend would then either query this information using REST or using an initial state as you see fit.
  4. Finally, there could be a way to query this using OCS, but I have not looked into it yet. As I do not see your use-case yet and what you want to achieve, you might want to be a bit more verbose before I invest too much time digging into the wrong direction.

Chris

1 Like