chore: move dev docs to opencloud-eu/docs repo (#635)

This commit is contained in:
Alex
2025-04-09 17:20:40 +02:00
committed by GitHub
parent 7e9a7d8099
commit f2ee327295
28 changed files with 0 additions and 2274 deletions

View File

@@ -1,16 +0,0 @@
---
when:
- event: ["push", "manual"]
branch: main
steps:
- name: devdocs
image: codeberg.org/xfix/plugin-codeberg-pages-deploy:1
settings:
folder: docs
branch: docs
git_config_email: ${CI_COMMIT_AUTHOR_EMAIL}
git_config_name: ${CI_COMMIT_AUTHOR}
ssh_key:
from_secret: ssh_key

View File

@@ -1,91 +0,0 @@
---
title: 'Getting Started'
sidebar_position: 1
---
## Installation
### Docker
Make sure to have Docker, Docker-Compose, Node.js and pnpm installed.
:::note
This setup currently doesn't work on Windows out of the box.
<details>
<summary>Workaround</summary>
One of our contributors has opened a PR to a dependency that prevents us from successfully bundling the frontend.
Feel free to check out [their changes](https://github.com/egoist/rollup-plugin-postcss/pull/384) and build them locally if you absolutely want to work on Windows.
</details>
:::
After cloning the [source code](https://github.com/opencloud-eu/web), install the dependencies via `pnpm install` and bundle the frontend code by running `pnpm build:w`.
Then, you can start the server by running `docker-compose up opencloud` and access it via [https://host.docker.internal:9200](https://host.docker.internal:9200). If you're not using Docker Desktop, you might have to modify your `/etc/hosts` and add `127.0.0.1 host.docker.internal` to make the `host.docker.internal` links work.
The bundled frontend code automatically gets mounted into the Docker containers, recompiles on changes and you can log in using the demo user (admin/admin) and take a look around!
For more details on how to set up Web for development, please see [tooling](./20-development/30-tooling.md).
## Source Code
The source code is hosted at [https://github.com/opencloud-eu/web](https://github.com/opencloud-eu/web).
## Configuration
There are sample config files provided in the [config folder](https://github.com/opencloud-eu/web/tree/main/config) of the OpenCloud Web git repository. See below for some configuration details.
- `customTranslations` You can specify one or multiple files to overwrite existing translations. For example set this option to `[{url: "https://localhost:9200/customTranslations.json"}]`.
#### Options
- `options.accountEditLink` This accepts an object with the following optional fields to have a link on the account page:
- `options.accountEditLink.href` Set a different target URL for the edit link. Make sure to prepend it with `http(s)://`.
- `options.disableFeedbackLink` Set this option to `true` to disable the feedback link in the topbar. Keeping it enabled (value `false` or absence of the option)
allows OpenCloud to get feedback from your user base through a dedicated survey website.
- `options.feedbackLink` This accepts an object with the following optional fields to customize the feedback link in the topbar:
- `options.feedbackLink.href` Set a different target URL for the feedback link. Make sure to prepend it with `http(s)://`. Defaults to `https://opencloud.eu/web-design-feedback`.
- `options.feedbackLink.ariaLabel` Since the link only has an icon, you can set an e.g. screen reader accessible label. Defaults to `OpenCloud feedback survey`.
- `options.feedbackLink.description` Provide any description you want to see as tooltip and as accessible description. Defaults to `Provide your feedback: We'd like to improve the web design and would be happy to hear your feedback. Thank you! Your OpenCloud team.`
- `options.sharingRecipientsPerPage` Sets the amount of users shown as recipients in the dropdown when sharing resources. Default amount is 200.
- `options.runningOnEos` Set this option to `true` if running on an [EOS storage backend](https://eos-web.web.cern.ch/eos-web/) to enable its specific features. Defaults to `false`.
- `options.cernFeatures` Enabling this will activate CERN-specific features. Defaults to `false`.
- `options.editor.autosaveEnabled` Specifies if the autosave for the editor apps is enabled.
- `options.editor.autosaveInterval` Specifies the time interval for the autosave of editor apps in seconds.
- `options.editor.openAsPreview` Specifies if non-personal files i.e. files in shares, spaces or public links are being opened in read only mode so the user needs to manually switch to edit mode. Can be set to `true`, `false` or an array of web app/editor names.
- `options.contextHelpersReadMore` Specifies whether the "Read more" link should be displayed or not.
- `options.tokenStorageLocal` Specifies whether the access token will be stored in the local storage when set to `true` or in the session storage when set to `false`. If stored in the local storage, login state will be persisted across multiple browser tabs, means no additional logins are required. Defaults to `true`.
- `options.loginUrl` Specifies the target URL to the login page. This is helpful when an external IdP is used. This option is disabled by default. Example URL like: 'https://www.myidp.com/login'.
- `options.logoutUrl` Adds a link to the user's profile page to point him to an external page, where he can manage his session and devices. This is helpful when an external IdP is used. This option is disabled by default.
- `options.userListRequiresFilter` Defines whether one or more filters must be set in order to list users in the Web admin settings. Set this option to 'true' if running in an environment with a lot of users and listing all users could slow down performance. Defaults to `false`.
- `options.concurrentRequests` This accepts an object with the following optional fields to customize the maximum number of concurrent requests in code paths where we limit concurrent requests
- `resourceBatchActions` Concurrent number of file/folder/space batch actions like e.g. accepting shares. Defaults to 4.
- `sse` Concurrent number of SSE event handlers. Defaults to 4.
- `shares` Accepts an object regarding the following sharing related options:
- `create` Concurrent number of share invites. Defaults to 4.
- `list` Concurrent number of individually loaded shares. Defaults to 2.
#### Scripts and Styles
Web supports adding additional CSS and JavaScript files to further customize the user experience and adapt it to your specific needs. Please consider opening a feature request if you feel like your customization could be a benefit to the upstream project, and keep an eye open for future major releases of `web` since this API may change.
- `styles` expects an array of objects that specify a `href` attribute, pointing to the path/URL of your stylesheet, like `[{ "href": "css/custom.css" }]`.
- `scripts` expects an array of objects that specify a `src` attribute, pointing to the path/URL of your script, and an optional `async` attribute (defaults to false), like `[{ "src": "js/custom.js", "async": true }]`.
### Sentry
Web supports [Sentry](https://sentry.io/welcome/) to provide monitoring and error tracking.
To enable sending data to a Sentry instance, you can use the following configuration keys:
- `sentry.dsn` Should contain the DSN for your sentry project.
- `sentry.environment`: Lets you specify the environment to use in Sentry. Defaults to `production`.
Any other key under `sentry` will be forwarded to the Sentry initialization. You can find out more
settings in the [Sentry docs](https://docs.sentry.io/platforms/javascript/configuration/).
:::note
If you are using an old version of Sentry (9 and before), you might want to add the setting `sentry.autoSessionTracking: false` to avoid errors related to breaking changes introduced in the
integration libraries.
:::

View File

@@ -1,52 +0,0 @@
---
title: 'Conventions'
sidebar_position: 1
id: conventions
---
This is a collection of tips and conventions to follow when working on the [OpenCloud Web frontend](https://github.com/opencloud-eu/web).
Since it is a living document, please open a PR if you find something missing.
## Contributing to OpenCloud Web
Everyone is invited to contribute. Simply fork the [codebase](https://github.com/opencloud-eu/web/),
check the [issues](https://github.com/opencloud-eu/web/issues?q=is%3Aopen%20is%3Aissue%20label%3AType%3AGood-First-Issue)
for a suitable one and open a pull request!
### Linting and Tests
To make sure your pull request can be efficiently reviewed and won't need a lot of changes down the road, please run the linter and
the unit tests via `pnpm lint --fix` and `pnpm test:unit` locally. Our [CI](https://drone.opencloud.eu/opencloud/web) will run on
pull requests and report back any problems after that. For a further introduction on how we handle testing, please head to
the [testing docs](./../30-testing/index.md).
## Code Conventions
### Early Returns
We're trying to stick with early returns in our code to make it more performant and simpler to reason about it.
### Translations
Use the `v-text` directive in combination with `$gettext` (or a variation of it) inside HTML tags (instead of
a `<translate tag="h1">` or similar) in order to make reasoning about the DOM tree easier.
### TypeScript
We're using TypeScript, which allows us to catch bugs at transpile time. Clean types make sure our IDEs can support us
in reasoning about our (ever growing, complex) codebase.
### Vue 3 and Composition API
We've migrated from Vue 2 to Vue 3 late in 2022 and since then have been investing continuous efforts to move away from the Vue options API
in favor of the Vue composition API. The `web-pkg` helper package provides quite some composables which will help you in
app & extension development, so we encourage you to make use of the Vue composition API as well, even outside of the
OpenCloud Web repository.
### Dependencies
To keep the bundle size small and reduce the risk of introducing security problems for our users, we try to limit
the amount of dependencies in our code base and keep them as up-to-date as possible.

View File

@@ -1,123 +0,0 @@
---
title: 'Repo structure and (published) packages'
sidebar_position: 2
id: repo-structure
---
## Repository Structure
From a developer's perspective, the most important parts of the [OpenCloud Web repo](https://github.com/opencloud-eu/web) are the following files and folders:
### dev Folder and docker-compose.yml File
The `/dev` folder contains all the configuration files that are needed in the `docker-compose.yml` file. This docker compose stack
contains all the backend and testing related infrastructure that is needed for an out-of-the-box usable localhost development setup,
as described in the [tooling section](./../20-development/30-tooling.md).
### packages Folder
We're using the [OpenCloud Web repo](https://github.com/opencloud-eu/web) as a mono repo. It contains a variety of packages. Some of them get
published to [npmjs.com](https://npmjs.com), others define the core packages, apps and extensions that are the foundation of
the `OpenCloud Web` release artifact.
Having these packages side by side within the `/packages` folder of the repo is possible because of a `pnpm` feature called `Workspaces`. You can learn more about that by visiting the [pnpm docs](https://pnpm.io/workspaces).
### tests Folder
We're using [Playwright](https://playwright.dev) for UI testing. The UI tests are located in `/tests/e2e`.
You're more than welcome to make a pull request and adjust this section of the docs accordingly. :-)
You can read more about testing in our [testing section](./../30-testing/index.md)
### package.json File
This is probably no surprise: the root level `package.json` file defines the project information, build scripts, dependencies and some more details.
Each package in `/packages` can and most likely will contain another `package.json` which does the same for the respective package.
### vite.config.ts
We're working with [Vite](https://vitejs.dev) as a local development server and build tool. `vite.config.ts` is the main configuration file for that.
You can read more about the usage in our [tooling section](./../20-development/30-tooling.md).
## (Published) Packages
Each package in the `/packages` folder can - not exclusively, but most commonly - consist of
- source code (`/src`),
- unit tests (`/tests`),
- translations (`/l10n`) and
- a `package.json` file for package specific details and dependencies.
### Code Style and Build Config
Some of our packages in `/packages` are pure helper packages which ensure a common code style and build configuration for all our
internal (mono repo) and external packages. We encourage you to make use of the very same packages. This helps the community
understand code more easily, even when coming from different developers or vendors in the OpenCloud Web ecosystem.
Namely those packages are
- `/packages/eslint-config`
- `/packages/extension-sdk`
- `/packages/prettier-config`
- `/packages/tsconfig/`
### OpenCloud Design System
The OpenCloud Design System (`/packages/design-system`) is a collection of components, design tokens and styles which ensure a
unique and consistent look and feel and branding throughout the OpenCloud Web ecosystem. We hope that you use it, too, so that your
very own apps and extensions will blend in with all the others. Documentation and code examples can be found in
the [design system documentation](https://opencloud.design).
The OpenCloud Design System is a standalone project, but to make development easier we have the code in our mono repo.
We're planning to publish it on npmjs.com as [@opencloud-eu/design-system](https://www.npmjs.com/package/@opencloud-eu/design-system)
as soon as possible. Since it's bundled with OpenCloud Web, you should not bundle it with your app or extension.
### web-client
The client package (`/packages/web-client`) serves as an abstraction layer for the various OpenCloud APIs, like
[LibreGraph](https://docs.opencloud.eu/apis/http/graph/), [WebDAV](https://docs.opencloud.eu/server/next/developer_manual/webdav_api/) and
[OCS](https://docs.opencloud.eu/server/next/developer_manual/core/apis/ocs-capabilities.html). The package provides TypeScript
interfaces for various entities (like files, folders, shares and spaces) and makes sure that raw API responses are properly
transformed so that you can deal with more useful objects. The web-client package gets published
on npmjs.com as [@opencloud-eu/web-client](https://www.npmjs.com/package/@opencloud-eu/web-client).
Dedicated documentation for the `web-client` package is not available, yet, since our extension system is still work in progress. However, the package's [README.md](https://github.com/opencloud-eu/web/blob/main/packages/web-client/README.md) gives you a few examples on how to use it.
### web-pkg
The web-pkg package (`/packages/web-pkg`) is a collection of opinionated components, composables, types and other helpers that aim
at making your app and extension developer experience as easy and seamless as possible. The web-pkg package gets published on
npmjs.com as [@opencloud-eu/web-pkg](https://www.npmjs.com/package/@opencloud-eu/web-pkg).
Dedicated documentation for the `web-pkg` package is not available, yet, since our extension system is still work in progress.
### web-runtime
At the very heart of OpenCloud Web, the `web-runtime` is responsible for dependency injection, app bootstrapping, configuration,
authentication, data preloading and much more.
It is very likely that you will never get in touch with it as most of the developer-facing features are exposed via `web-pkg`. If you
have more questions about this package, please write an issue in our [issue tracker](https://github.com/opencloud-eu/web/issues).
### Standalone Core Apps
Both `web-app-admin-settings` and `web-app-files` are standalone apps which are bundled with the default OpenCloud Web release artifact.
### Viewer and Editor Apps
Apps which fall into the categories `viewer` or `editor` can be opened from the context of a file or folder. This mostly happens from
within the `files` app. We currently bundle the following apps with the default OpenCloud Web release artifact:
- `web-app-epub-reader` a simple reader for `.epub` files
- `web-app-external` an iframe integration of all the apps coming from the [app provider](https://docs.opencloud.eu/services/app-provider/)
(e.g. OnlyOffice, Collabora Online and others)
- `web-app-pdf-viewer` a viewer for `.pdf` files, which relies on native PDF rendering support from the browser
- `web-app-preview` a viewer for various media files (audio / video / image formats)
- `web-app-text-editor` a simple editor for `.txt`, `.md` and other plain text files
If you're interested in writing your own viewer or editor app for certain file types, please get in touch with us for more info.
### Testing
Additional unit testing code lives in `test-helpers`.

View File

@@ -1,42 +0,0 @@
---
title: 'Tooling'
sidebar_position: 3
id: tooling
---
## Packaging
Web is using [pnpm](https://pnpm.io/) as package manager and [vite](https://vitejs.dev/) as build tool. The latter is built on top of [rollup](https://rollupjs.org/) and brings some additional features such as instant hot-reloading.
## Development Setup
### Prerequisites
- docker
- docker-compose (if not already included in your docker installation)
- pnpm
- node
If youre not using Docker Desktop, you might have to modify your `/etc/hosts` and add `127.0.0.1 host.docker.internal` to make `host.docker.internal` links work.
### Installing Dependencies
After cloning the source code, install the dependencies via `pnpm install`.
### Starting the Server
You can start the server by running `docker-compose up opencloud`.
Note that the container needs a short while to start because it is waiting for `tika` to be initialized. This is the case as soon as the `tika-service` container has stopped running.
### Building and Accessing Web
After the docker containers are running (and `tika` is being initialized), run `pnpm build:w` to build Web. This also includes hot-reloading after changes you make, although it will take a while to rebuild the project. See down below for some details on how to enable instant hot-reloading.
Now you can access Web via https://host.docker.internal:9200.
### Using Instant Hot-Reload via Vite
To work with instant hot-reloading, you can also build Web by running `pnpm vite`. The port to access Web is slightly different then: https://host.docker.internal:9201. Also note that the initial page load may take a bit longer than usual. This is normal and to be expected.

View File

@@ -1,4 +0,0 @@
{
"label": "Development",
"position": 2
}

View File

@@ -1,10 +0,0 @@
---
title: 'Development'
---
This section is a guide about the development of OpenCloud Web **core**, **apps** and **extensions**.
This includes **tooling**, **conventions** and the **repo structure**.
It is of interest for you if you want to contribute to OpenCloud Web or develop your own apps and extensions.

View File

@@ -1,155 +0,0 @@
---
title: 'Running tests'
sidebar_position: 1
id: running-tests
---
## Introduction
In order to allow us to make changes quickly, often and with a high level of confidence, we heavily rely on tests within the `web` repository.
All the steps below require you to have the `web` repo cloned locally and dependencies installed.
This can be achieved by running
```shell
$ git clone https://github.com/opencloud-eu/web.git
$ cd web
$ pnpm install
```
### Unit Tests
We have a steadily growing coverage of unit tests. You can run them locally via
```shell
$ pnpm test:unit
```
You can also specify which tests to run by giving a path param, like so: `pnpm test:unit packages/<app-name>/tests/unit/path/to/test.spec.js`.
#### Unit Test File Structure
Our unit tests spec files follow a simple structure:
- fixtures and mocks at the top
- helper functions at the bottom
- tests in between
We usually organize tests with nested `describe` blocks. If you would like to get feedback from the core team about
the structure, scope and goals of your unit tests before actually writing some, we invite you to make a pull request
with only `describe` blocks and nested `it.todo("put your test description here")` lines.
### E2E Tests (Playwright)
Our end-to-end test suite is built upon the [Playwright Framework](https://github.com/microsoft/playwright),
which makes it easy to write tests, debug them and have them run cross-browser with minimal overhead.
#### Preparation
Please make sure you have installed all dependencies and started the server(s) as described in [tooling](./../20-development/30-tooling.md).
#### Prepare & Start Web
Bundle the web frontend with the following command:
```shell
$ pnpm build:w
```
Our compose setup automatically mounts it into an OpenCloud backend, respectively. Web also gets recompiled on changes.
#### Run E2E Tests
The following command will run all available e2e tests:
```shell
$ pnpm test:e2e:cucumber 'tests/e2e/cucumber/**/*.feature'
```
#### Options
To run a particular test, simply add the feature file and line number to the test command, e.g. `pnpm test:e2e:cucumber tests/e2e/cucumber/features/smoke/admin-settings/users.feature:84`
Various options are available via ENV variables, e.g.
- `OC_BASE_URL` # use your OpenCloud URL. Default value: host.docker.internal:9200
- `BASIC_AUTH=true` use basic authorization for api requests.
- `RETRY=n` to retry failures `n` times
- `SLOW_MO=n` to slow the execution time by `n` milliseconds
- `TIMEOUT=n` to set tests to timeout after `n` milliseconds
- `HEADLESS=bool` to open the browser while the tests run (defaults to true => headless mode)
- `BROWSER=name` to run tests against a specific browser. Defaults to chromium, available are chromium, firefox, webkit, chromium
- `ADMIN_PASSWORD` to set administrator password. By default, the `admin` password is used in the test
- `PARALLEL` for parallel test execution
For debugging reasons, you may want to record a video or traces of your test run.
Again, you can use the following ENV variables in your command:
- `REPORT_DIR=another/path` to set a directory for your recorded files (defaults to "reports")
- `REPORT_VIDEO=true` to record a video of the test run
- `REPORT_HAR=true` to save request information from the test run
- `REPORT_TRACING=true` to record traces from the test run
To then open e.g. the tracing from the `REPORT_DIR`, run
```shell
$ npx playwright show-trace path/to/file.zip
```
#### Lint E2E Test Code
Run the following command to find out the lint issues early in the test codes:
```shell
$ pnpm lint
```
And to fix the lint problems run the following command:
```shell
$ pnpm lint --fix
```
If the lint problems are not fixed by `--fix` option, we have to manually fix the code.
### Analyze the Test Report
The cucumber library is used as the test runner for e2e tests. The report generator script lives inside the `tests/e2e/cucumber/report` folder. If you want to create a report after the tests are done, run the command:
```bash
node tests/e2e/cucumber/report --report-input=tests/e2e/cucumber/report/report.json
```
By default, the report gets generated to reports/e2e/cucumber/releaseReport/cucumber_report.html.
The location can be changed by adding the `--report-location` flag.
To see all available options run
```bash
node tests/e2e/cucumber/report --help
```
### E2E Tests on OpenCloud With Keycloak
We can run some of the e2e tests on OpenCloud setup with Keycloak as an external idp. To run tests against locally, please follow the steps below:
#### Run OpenCloud With Keycloak
There's a documentation to serve [OpenCloud with Keycloak](https://docs.opencloud.eu/opencloud/deployment/opencloud_keycloak/). Please follow each step to run **OpenCloud with Keycloak**.
#### Run E2E Tests
```bash
KEYCLOAK=true \
BASE_URL_OPEN_CLOUD=demo.opencloud.test \
pnpm run test:e2e:cucumber tests/e2e/cucumber/features/journeys
```
Following environment variables come in use while running e2e tests on OpenCloud with Keycloak:
- `BASE_URL_OPENCLOUD` sets OpenCloud url (e.g.: demo.opencloud.test)
- `KEYCLOAK_HOST` sets Keycloak url (e.g.: keycloak.opencloud.test)
- `KEYCLOAK=true` runs the tests with Keycloak
- `KEYCLOAK_REALM` sets OpenCloud realm name used on Keycloak

View File

@@ -1,647 +0,0 @@
---
title: 'End-to-End (E2E) Test Standards'
sidebar_position: 2
id: e2e-testing-standards
---
## Introduction
In OpenCloud, we use Playwright for webUI test automation. We benefit from lower barriers to entry, readability, and usability when test standards are consistent across repositories. For example:
- **Reusability:** Enhances reusability by making it easier to reuse functions, locators, shared steps, and other test code. Finding the necessary components and functionalities will be simple for someone working on a test resulting in reduced duplication and the requirement for rework after the initiation of a code review.
- **Facilitates reviews:** Code can be examined more quickly. Since you can quickly determine what the test code is doing, it eases the mental strain of code review.
- **Faster onboarding:** By following naming conventions, new contributors are onboarded more rapidly and feel comfortable enough to contribute to the codebase.
Here are the test standards and guidelines we adhere to when creating Playwright tests at OpenCloud.
## Folder Structure:
- `tests/:`
- `e2e/`: Main folder containing all (end-to-end) E2E test-related files.
- `cucumber/`: Main folder containing all Cucumber(BDD) test-related files.
- `features/`: Contains Gherkin feature files.
- `<test-suite-folder>/`: Collection house for "**related"** feature files.
- `<aFeatureFile>.feature`: A feature file.
- `steps/`: Holds the step definition files for mapping Gherkin steps to code.
- `<stepDefinition>.ts`: Step definitions for each feature.
- `hooks/`: Cucumber hooks for setting up and tearing down test environments.
- `hooks.ts`: Contains `Before`, `After`, and other lifecycle hooks.
- `support/`: Playwright (Test implementation)
- `api/`: Contains API-related test files and configurations.
- `<api-folder>/ `: Specific API tests for a particular service.
- `objects/`: Contains the Page Object classes.
- `<specific-page-object-folder>/`: Collection house for related page objects for each webpage or component.
- `<individualPageObject>.ts`: Page Object for each webpage or component.
- `utils/`: Utility functions and common helpers.
- `helpers.ts`: Common utility functions (e.g., date formatting, data generation).
- `test-data/`: Static test data files or folders for upload.
- `filesForUpload/`: Static test data files for upload.
- `config/`: Configuration files for Playwright and other tools.
- `playwright.config.ts`: Playwright configuration.
- `reports/`: Generated test reports (e.g., HTML, JSON).
- `screenshots/`: Captured screenshots during test execution.
- `videos/`: Recorded videos of test runs.
## Test Structure - Arrange, Act, Assert:
We can follow the AAA (Arrange, Act, Assert) pattern when structuring the tests. In most cases, the Arrange step can be included in a Before block(hook).
Consider including comments defining each section to ease readability.
```typescript
// arrange, set up the initial conditions for the test
// create a property
await createProperty()
// act, perform the action that you want to test
// raise a charge
// this could involve calling methods or functions defined in your page objects
await raiseCharge()
// assert, verify that the action had the expected outcome
// confirm charge has been raised
expect(charge).toBe('raised')
```
## Page Object Model (POM)
Every page should possess one POM file to enhance maintainability and scalability of our tests. It should include all the selectors and functions which are needed to interact with the UI page or component(s).
All interactions should be done using the page objects, no selectors in your tests.
All assertions should be in your test, no assertion in the POM
DO 👍
```typescript
// POM file './pageOobjects/foo/'
// add all locators and functions related to the page.
// allowing all tests to reuse
import { expect, Locator, Page } from '@playwright/test'
export class FooPage {
readonly errorMessage: Locator
constructor(page: Page) {
this.page = page
this.errorMessage = page.locator('.error-message')
}
}
// test file './steps/foo.ts'
import { FooPage } from './pageObjects/foo'
let fooPage: FooPage
Then('error message should be visible', async function ({ page }) {
const fooPage = new FooPage({ page })
await expect(fooPage.errorMessage).toBeVisible()
})
```
DO NOT ⚔️
```typescript
// test file './steps/foo.ts'
// include locators directly in test
import { Locator, Page } from '@playwright/test'
Then('error message should be visible', async function ({ page }) {
await expect(page.locator('.error-message')).toBeVisible()
})
```
## Waiting:
Playwright uses auto-waiting, so we avoid artificial waiting, the exception being if it is really necessary.
DO NOT ⚔️
```typescript
await page.waitForTimeout(5000)
```
This can cause flaky tests as we can rarely be certain the amount of wait time is enough. It can also unnecessarily increase the test run time. Instead, we can try:
DO 👍
```typescript
await page.goto(fooBarURL, {
waitUntil: 'domcontentloaded'
})
```
DO 👍
```typescript
const element = page.locator('some-locator-path')
element.waitFor({ visible: true })
```
DO 👍
```typescript
await fooPage.buttonFoo.click()
await expect(fooPage.titlePage).toBeVisible()
```
## Selectors
Avoid selectors tied to implementation and page structure.
Instead, we can prioritize the below, based on [testing-library guiding principles](https://testing-library.com/docs/queries/about/#priority)
- `getByRole` (this aids accessibility, reflects how users and assistive technology perceive the page)
- `getByText`
- `getByTestId` (add them when needed)
DO NOT ⚔️
```javascript
page.locator('.opt-u > div > .summary > div:nth-child(4) > div')
```
DO 👍
```javascript
page.locator('#foo-button')
page.getByText('OK')
```
## Naming Conventions
### Files and folders
- **Files**: Declare in **_camelCase_**
- **Folders**: Declare in **_kebab-case_**
### Variables
Declare in **_camelCase_**.
### Booleans
Start with is, has, are, have. This helps spot that this is a boolean while skimming the code. Still declared in **_camelCase_**.
```typescript
let isTurnedOn = false
```
### Page Objects / Classes
Declare in **_PascalCase_**.
Use descriptive naming, which can help the reader quickly identify what page or page component this is covering. Use as much context as needed from your product to make the name meaningful.
DO NOT ⚔️
```typescript
export class newModal
```
DO 👍
```typescript
export class AddWorksOrderModal
```
### Locators
Use descriptive naming, which can help the reader quickly identify what element the locator is targeting.
As an example, you can use a naming structure that contains **_“action / name of element” + “type of element”_**.
**_Defining type of element_** - These are your basic HTML element types, theyll be defined and named in the design system, or as a team you can align on a consistent naming of the elements. Example: checkbox, tickbox, button, tooltip
**_Defining action / name -_** Think about what action this element will perform when interacted with. Or any existing name/text of the element
DO 👍
```typescript
// This element is a submit button for the user registration form
const submitButton = await page.locator('<locator-path>')
```
DO 👍
```typescript
// This element is a button for uploading a profile picture
const uploadProfilePictureButton = await page.locator('<locator-path>')
```
### Function names
Always start function names with a **_“verb”_**, followed by the **_“component context”_** that the function is interacting with i.e. what entity it is having an effect on.
DO 👍
```typescript
getWorksOrder()
printTransactions()
deleteProperty()
```
## Gherkin Best Practices: Do's and Don'ts for Effective Features & Scenarios
### Writing Features
DO 👍
- **Be Descriptive:** Use clear and descriptive language that accurately reflects the functionality.
- **Keep It Concise:** Avoid overly long titles or descriptions. Aim for brevity while maintaining clarity.
- **Use Active Voice:** Write in an active voice to make it clear who is performing the action.
- **Contextual Information:** If applicable, provide context about the user role or the scenario to clarify who benefits from the feature.
DO NOT ⚔️
- **Avoid Vague Titles:** Titles like "Test Feature" or "Feature 1" do not provide meaningful information.
- **Neglect User Perspective:** Failing to mention the user role can make it unclear who the feature is intended for.
Example of a well-written feature:
```gherkin
Feature: Password Management for Registered Users
As a registered user
I want to set a new password
So that I can secure my account
```
### Writing Scenarios
- **Use Clear and Descriptive Scenario Titles:** Ensure that each scenario title clearly conveys the action being tested and the expected outcome.
```gherkin
Scenario: User successfully registers with valid details
```
- **Use Clear Given/When/Then Steps:** Clearly define the context, action, and expected outcome(success or error messages if any).
```gherkin
Given the user is on the registration page
When the user enters valid details
Then the user should see a confirmation message "Registration successful! Welcome to our platform."
```
- **Use "tries to" for Negation:** This syntax is effective for scenarios where an action is expected to fail.
```gherkin
Scenario: User tries to log in with incorrect credentials
Given the user is on the registration page
When the user tries to log in with username "Alice" and password "wrongPassword"
Then the user should see an error message
"""
Incorrect username or password.
"""
```
## Broad Gherkin Guidelines
This [OpenCloud developer manual](https://docs.opencloud.eu/server/next/developer_manual/testing/acceptance-tests.html#how-to-write-acceptance-tests) provides comprehensive guidelines and best practices for writing acceptance tests using the Gherkin syntax, a widely adopted language for defining test scenarios in a human-readable format. The manual outlines the specific syntax and structure required when crafting feature files and scenarios to ensure consistency and maintainability within the OpenCloud testing framework.
In OpenCloud, we use Playwright for webUI test automation. We benefit from lower barriers to entry, readability, and usability when test standards are consistent across repositories. For example:
- **Reusability:** Enhances reusability by making it easier to reuse functions, locators, shared steps, and other test code. Finding the necessary components and functionalities will be simple for someone working on a test resulting in reduced duplication and the requirement for rework after the initiation of a code review.
- **Facilitates reviews:** Code can be examined more quickly. Since you can quickly determine what the test code is doing, it eases the mental strain of code review.
- **Faster onboarding:** By following naming conventions, new contributors are onboarded more rapidly and feel comfortable enough to contribute to the codebase.
Here are the test standards and guidelines we adhere to when creating Playwright tests at OpenCloud.
## Folder Structure:
- `tests/:`
- `e2e/`: Main folder containing all (end-to-end) E2E test-related files.
- `cucumber/`: Main folder containing all Cucumber(BDD) test-related files.
- `features/`: Contains Gherkin feature files.
- `<test-suite-folder>/`: Collection house for "**related"** feature files.
- `<aFeatureFile>.feature`: A feature file.
- `steps/`: Holds the step definition files for mapping Gherkin steps to code.
- `<stepDefinition>.ts`: Step definitions for each feature.
- `hooks/`: Cucumber hooks for setting up and tearing down test environments.
- `hooks.ts`: Contains `Before`, `After`, and other lifecycle hooks.
- `support/`: Playwright (Test implementation)
- `api/`: Contains API-related test files and configurations.
- `<api-folder>/ `: Specific API tests for a particular service.
- `objects/`: Contains the Page Object classes.
- `<specific-page-object-folder>/`: Collection house for related page objects for each webpage or component.
- `<individualPageObject>.ts`: Page Object for each webpage or component.
- `utils/`: Utility functions and common helpers.
- `helpers.ts`: Common utility functions (e.g., date formatting, data generation).
- `test-data/`: Static test data files or folders for upload.
- `filesForUpload/`: Static test data files for upload.
- `config/`: Configuration files for Playwright and other tools.
- `playwright.config.ts`: Playwright configuration.
- `reports/`: Generated test reports (e.g., HTML, JSON).
- `screenshots/`: Captured screenshots during test execution.
- `videos/`: Recorded videos of test runs.
## Test Structure - Arrange, Act, Assert:
We can follow the AAA (Arrange, Act, Assert) pattern when structuring the tests. In most cases, the Arrange step can be included in a Before block(hook).
Consider including comments defining each section to ease readability.
```typescript
// arrange, set up the initial conditions for the test
// create a property
await createProperty()
// act, perform the action that you want to test
// raise a charge
// this could involve calling methods or functions defined in your page objects
await raiseCharge()
// assert, verify that the action had the expected outcome
// confirm charge has been raised
expect(charge).toBe('raised')
```
## Page Object Model (POM)
Every page should possess one POM file to enhance maintainability and scalability of our tests. It should include all the selectors and functions which are needed to interact with the UI page or component(s).
All interactions should be done using the page objects, no selectors in your tests.
All assertions should be in your test, no assertion in the POM
DO 👍
```typescript
// POM file './pageOobjects/foo/'
// add all locators and functions related to the page.
// allowing all tests to reuse
import { expect, Locator, Page } from '@playwright/test'
export class FooPage {
readonly errorMessage: Locator
constructor(page: Page) {
this.page = page
this.errorMessage = page.locator('.error-message')
}
}
// test file './steps/foo.ts'
import { FooPage } from './pageObjects/foo'
let fooPage: FooPage
Then('error message should be visible', async function ({ page }) {
const fooPage = new FooPage({ page })
await expect(fooPage.errorMessage).toBeVisible()
})
```
DO NOT ⚔️
```typescript
// test file './steps/foo.ts'
// include locators directly in test
import { Locator, Page } from '@playwright/test'
Then('error message should be visible', async function ({ page }) {
await expect(page.locator('.error-message')).toBeVisible()
})
```
## Waiting:
Playwright uses auto-waiting, so we avoid artificial waiting, the exception being if it is really necessary.
DO NOT ⚔️
```typescript
await page.waitForTimeout(5000)
```
This can cause flaky tests as we can rarely be certain the amount of wait time is enough. It can also unnecessarily increase the test run time. Instead, we can try:
DO 👍
```typescript
await page.goto(fooBarURL, {
waitUntil: 'domcontentloaded'
})
```
DO 👍
```typescript
const element = page.locator('some-locator-path')
element.waitFor({ visible: true })
```
DO 👍
```typescript
await fooPage.buttonFoo.click()
await expect(fooPage.titlePage).toBeVisible()
```
## Selectors
Avoid selectors tied to implementation and page structure.
Instead, we can prioritize the below, based on [testing-library guiding principles](https://testing-library.com/docs/queries/about/#priority)
- `getByRole` (this aids accessibility, reflects how users and assistive technology perceive the page)
- `getByText`
- `getByTestId` (add them when needed)
DO NOT ⚔️
```javascript
page.locator('.opt-u > div > .summary > div:nth-child(4) > div')
```
DO 👍
```javascript
page.locator('#foo-button')
page.getByText('OK')
```
## Naming Conventions
### Files and folders
- **Files**: Declare in **_camelCase_**
- **Folders**: Declare in **_kebab-case_**
### Variables
Declare in **_camelCase_**.
### Booleans
Start with is, has, are, have. This helps spot that this is a boolean while skimming the code. Still declared in **_camelCase_**.
```typescript
let isTurnedOn = false
```
### Page Objects / Classes
Declare in **_PascalCase_**.
Use descriptive naming, which can help the reader quickly identify what page or page component this is covering. Use as much context as needed from your product to make the name meaningful.
DO NOT ⚔️
```typescript
export class newModal
```
DO 👍
```typescript
export class AddWorksOrderModal
```
### Locators
Use descriptive naming, which can help the reader quickly identify what element the locator is targeting.
As an example, you can use a naming structure that contains **_“action / name of element” + “type of element”_**.
**_Defining type of element_** - These are your basic HTML element types, theyll be defined and named in the design system, or as a team you can align on a consistent naming of the elements. Example: checkbox, tickbox, button, tooltip
**_Defining action / name -_** Think about what action this element will perform when interacted with. Or any existing name/text of the element
DO 👍
```typescript
// This element is a submit button for the user registration form
const submitButton = await page.locator('<locator-path>')
```
DO 👍
```typescript
// This element is a button for uploading a profile picture
const uploadProfilePictureButton = await page.locator('<locator-path>')
```
### Function names
Always start function names with a **_“verb”_**, followed by the **_“component context”_** that the function is interacting with i.e. what entity it is having an effect on.
DO 👍
```typescript
getWorksOrder()
printTransactions()
deleteProperty()
```
## Gherkin Best Practices: Do's and Don'ts for Effective Features & Scenarios
### Writing Features
DO 👍
- **Be Descriptive:** Use clear and descriptive language that accurately reflects the functionality.
- **Keep It Concise:** Avoid overly long titles or descriptions. Aim for brevity while maintaining clarity.
- **Use Active Voice:** Write in an active voice to make it clear who is performing the action.
- **Contextual Information:** If applicable, provide context about the user role or the scenario to clarify who benefits from the feature.
DO NOT ⚔️
- **Avoid Vague Titles:** Titles like "Test Feature" or "Feature 1" do not provide meaningful information.
- **Neglect User Perspective:** Failing to mention the user role can make it unclear who the feature is intended for.
Example of a well-written feature:
```gherkin
Feature: Password Management for Registered Users
As a registered user
I want to set a new password
So that I can secure my account
```
### Writing Scenarios
- **Use Clear and Descriptive Scenario Titles:** Ensure that each scenario title clearly conveys the action being tested and the expected outcome.
```gherkin
Scenario: User successfully registers with valid details
```
- **Use Clear Given/When/Then Steps:** Clearly define the context, action, and expected outcome(success or error messages if any).
```gherkin
Given the user is on the registration page
When the user enters valid details
Then the user should see a confirmation message "Registration successful! Welcome to our platform."
```
- **Use "tries to" for Negation:** This syntax is effective for scenarios where an action is expected to fail.
```gherkin
Scenario: User tries to log in with incorrect credentials
Given the user is on the registration page
When the user tries to log in with username "Alice" and password "wrongPassword"
Then the user should see an error message
"""
Incorrect username or password.
"""
```
## Broad Gherkin Guidelines
This [OpenCloud developer manual](https://docs.opencloud.eu/server/next/developer_manual/testing/acceptance-tests.html#how-to-write-acceptance-tests) provides comprehensive guidelines and best practices for writing acceptance tests using the Gherkin syntax, a widely adopted language for defining test scenarios in a human-readable format. The manual outlines the specific syntax and structure required when crafting feature files and scenarios to ensure consistency and maintainability within the OpenCloud testing framework.

View File

@@ -1,4 +0,0 @@
{
"label": "Testing",
"position": 3
}

View File

@@ -1,5 +0,0 @@
---
title: 'Testing'
---
Guides on running different kinds of tests in OpenCloud Web.

View File

@@ -1,4 +0,0 @@
{
"label": "Embed Mode",
"position": 4
}

View File

@@ -1,134 +0,0 @@
---
title: 'Embed Mode'
---
The OpenCloud Web can be consumed by another application in a stripped down version called "Embed mode". This mode is supposed to be used in the context of selecting or sharing resources. If you're looking for even more minimalistic approach, you can take a look at the [File picker](https://docs.opencloud.eu/integration/file_picker/).
## Getting started
To integrate OpenCloud Web into your application, add an iframe element pointing to your OpenCloud Web deployed instance with additional query parameter `embed=true`.
```html
<iframe src="<web-url>?embed=true"></iframe>
```
## Communication
To establish seamless cross-origin communication between the embedded instance and the parent application, our approach involves emitting events using the `postMessage` method. These events can be conveniently captured by utilizing the standard `window.addEventListener('message', listener)` pattern.
### Target origin
By default, the `postMessage` method does not specify the `targetOrigin` parameter. However, it is recommended best practice to explicitly pass in the URI of the iframe origin (not the parent application). To enhance security, you can specify this value by modifying the config option `options.embed.messagesOrigin`.
### Events
To maintain uniformity and ease of handling, each event encapsulates the same structure within its payload: `{ name: string, data: any }`.
| Name | Data | Description |
| -------------------------- | ---------- | ------------------------------------------------------------------------------------- |
| **opencloud-embed:select** | Resource[] | Gets emitted when user selects resources or location via the select action |
| **opencloud-embed:share** | string[] | Gets emitted when user selects resources and shares them via the "Share links" action |
| **opencloud-embed:cancel** | null | Gets emitted when user attempts to close the embedded instance via "Cancel" action |
### Example
```html
<iframe src="https://my-opencloud-web-instance?embed=true"></iframe>
<script>
function selectEventHandler(event) {
if (event.data?.name !== 'opencloud-embed:select') {
return
}
const resources = event.data.data
doSomethingWithSelectedResources(resources)
}
window.addEventListener('message', selectEventHandler)
</script>
```
## Location picker
By default, the Embed mode allows users to select resources. In certain cases (e.g. uploading a file), this needs to be changed to allow selecting a location. This can be achieved by running the embed mode with additional parameter `embed-target=location`. With this parameter, resource selection is disabled and the selected resources array always includes the current folder as the only item.
In special scenarios you also want the user to set a file name, this can be achieved by adding the `embed-choose-file-name=true` parameter, or if you also want to set a default file name, you can use `embed-choose-file-name-suggestion=my file.text`.
### Example
```html
<iframe src="https://my-opencloud-web-instance?embed=true&embed-target=location"></iframe>
<script>
function selectEventHandler(event) {
if (event.data?.name !== 'opencloud-embed:select') {
return
}
const resources = event.data.data[0]
doSomethingWithSelectedResources(resources)
}
window.addEventListener('message', selectEventHandler)
</script>
```
## File picker
The File Picker mode in OpenCloud Web is designed for embedding an interface that allows users to pick a single file.
This mode can be configured to restrict the file types that users can select. To enable the File Picker mode, you need
to include the embed-target=file query parameter in the iframe URL. Furthermore, you can specify allowed file types
using the embed-file-types parameter. The file types can be specified using file extensions, MIME types, or a
combination of both. If the embed-file-types parameter is not provided, all file types will be selectable by default.
### Example
```html
<iframe
src="https://my-opencloud-web-instance?embed=true&embed-target=file&embed-file-types=txt,image/png"
></iframe>
<script>
function selectEventHandler(event) {
if (event.data?.name !== 'opencloud-embed:file-pick') {
return
}
const file = event.data.data
doSomethingWithPickedFile(file)
}
window.addEventListener('message', selectEventHandler)
</script>
```
## Delegate authentication
If you already have a valid `access_token` that can be used to call the API from within the Embed mode and do not want to force the user to authenticate again, you can delegate the authentication. Delegating authentication will disable internal login form in OpenCloud Web and will instead use events to obtain the token and update it.
### Configuration
To allow authentication delegation, you need to set the config option `options.embed.delegateAuthentication` to `true`. This can be achieved via query parameter `embed-delegate-authentication=true`. Because we are using the `postMessage` method to communicate across different origins, it is best practice to verify that the event originated from a known origin and not from some malicious site. We highly recommend to allow this check in production environments. You can enable it by setting the config option `options.embed.delegateAuthenticationOrigin` via query parameter `embed-delegate-authentication-origin=my-origin`. The value of this parameter will be compared against the `MessageEvent.origin` value and if they do not match, the token will be rejected.
### Events
#### Opening Embed mode
As already mentioned, we're using the `postMessage` method to allow communication between the Embed mode and the parent application. When the Embed mode is opened for the first time, the user gets redirected to the `/web-oidc-callback` page where a message with payload `{ name: 'opencloud-embed:request-token', data: undefined }` is sent to request the `access_token` from the parent application. The parent application should set an event listener before opening the Embed mode and once received, it should send a message with payload `{ name: 'opencloud-embed:update-token', data: { access_token: '<bearer-token>' } }`. Once the Embed mode receives this message, it will save the token in the application state and will automatically authenticate the user.
:::note
When passing the token in the message payload, use only the token itself without `Bearer` string as that will be added automatically in the Embed mode.
:::
:::note
To save unnecessary duplication of messages with only different names, the name in the message payload above is exactly the same for both the initial authentication and subsequent token updates after renewal.
:::
#### Updating the token
When authentication is delegated, the automatic renewal of the token inside of OpenCloud Web is disabled. In order to update the token, a listener is created which awaits a message with payload `{ name: 'opencloud-embed:update-token', data: { access_token: '<bearer-token>' } }`. The token will then be replaced inside of the Embed mode automatically.

View File

@@ -1,118 +0,0 @@
---
title: 'Viewer and editor apps'
sidebar_position: 1
---
## Viewer and editor apps
OpenCloud Web allows developers to implement apps for viewing and editing specific file types. For instance, the built-in preview app serves as the default application for opening media files like images, videos, or audio.
This section will guide you through the process of implementing such an app within OpenCloud Web.
### Basic app structure
An app is essentially a distinct package that must be specified as an external application in the Web configuration.
The structure of an app is quite simple and straightforward. Consider, for example, the [pdf-viewer app](https://github.com/opencloud-eu/web/tree/main/packages/web-app-pdf-viewer). It consists of a `package.json` file, a `src` directory containing all the source code, and a `l10n` directory for translations. Optionally, you may also include a `tests` directory if your application requires testing.
To learn more about apps in general, please refer to the [Web app docs](./index.md).
### App setup
Inside the `src` folder you will need an `index.ts` file that sets up the app so it can be registered by the Web runtime. It follows the basic structure as described in [the apps section](./index.md), so it may look like this:
```typescript
import { AppWrapperRoute, defineWebApplication, AppMenuItemExtension } from '@opencloud-eu/web-pkg'
import translations from '../l10n/translations.json'
import { useGettext } from 'vue3-gettext'
import { computed } from 'vue'
// This is the base component of your app.
import App from './App.vue'
export default defineWebApplication({
setup() {
// The ID of your app.
const appId = 'advanced-pdf-viewer'
const { $gettext } = useGettext()
// This creates a route under which your app can be opened.
// Later, this route will be bound to one or more file extensions.
const routes = [
{
name: 'advanced-pdf-viewer',
path: '/:driveAliasAndItem(.*)?',
component: AppWrapperRoute(App, {
applicationId: appId
}),
meta: {
authContext: 'hybrid',
title: $gettext('Advanced PDF Viewer'),
patchCleanPath: true
}
}
]
// if you want your app to be present in the app menu on the top left.
const menuItems = computed<AppMenuItemExtension[]>(() => [
{
label: () => $gettext('Advanced PDF Viewer'),
type: 'appMenuItem',
handler: () => {
// do stuff...
}
}
])
return {
appInfo: {
name: 'Advanced PDF Viewer',
id: appId,
defaultExtension: 'pdf',
extensions: [
// This makes sure all files with the "pdf" extension will be routed to your app when being opened.
// See the `ApplicationFileExtension` interface down below for a list of all possible properties.
{
extension: 'pdf',
routeName: 'advanced-pdf-viewer',
// Add this if you want your app to be present in the "New" file menu.
newFileMenu: {
menuTitle() {
return $gettext('PDF document')
}
}
}
]
},
routes,
translations,
extensions: menuItems
}
}
})
```
Here is the interface defining the `extensions` property of the `appInfo` object.
```typescript
interface ApplicationFileExtension {
app?: string
extension?: string
createFileHandler?: (arg: {
fileName: string
space: SpaceResource
currentFolder: Resource
}) => Promise<Resource>
hasPriority?: boolean
label?: string
name?: string
icon?: string
mimeType?: string
newFileMenu?: { menuTitle: () => string }
routeName?: string
}
```

View File

@@ -1,92 +0,0 @@
---
title: 'Action extensions'
sidebar_position: 1
id: action-extensions
---
## Action extension type
Actions are one of the possible extension types. Registered actions get rendered in various places across the UI, depending on their scope and targets.
### Configuration
This is what the `ActionExtension` interface looks like:
```typescript
interface ActionExtension {
id: string
type: 'action'
extensionPointIds?: string[]
action: Action // Please check the Action section below
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
#### Action
The most important configuration options are:
- `icon` - The icon to be displayed, can be picked from https://opencloud.design/#/Design%20Tokens/IconList
- `name` - The name of the action (not displayed in the UI)
- `label` - The text to be displayed
- `route` - The string/route to navigate to. The nav item will be a `<router-link>` tag.
- `href` - The URL to navigate to. The nav item will be a `<a>`tag.
- `handler` - The action to perform upon click. The nav item will be a `<button>` tag.
- `isVisible` - Determines whether the action is displayed to the user
Please check the [`Action` type](https://github.com/opencloud-eu/web/blob/236c185540fc6758dc7bd84985c8834fa4145530/packages/web-pkg/src/composables/actions/types.ts#L6) for a full list of configuration options.
### Example
The following example shows how an action extension for downloading files could look like. Note that the extension is wrapped inside a Vue composable so it can easily be reused. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/master/packages/web-pkg) package.
```typescript
export const useDownloadFilesExtension = () => {
const { $gettext } = useGettext()
const extension = computed<ActionExtension>(() => ({
id: 'com.github.opencloud-eu.web.files.download-action',
scopes: ['resource'],
type: 'action',
action: {
name: 'download-files',
icon: 'download',
class: 'oc-files-actions-download-files',
label: () => $gettext('Download'),
isVisible: ({ space, resources }) => {
if (resources.length === 0) {
return false
}
return true
},
handler: ({ space, resources }) => {
console.log('Triggering download...')
}
}
}))
return { extension }
}
```
The extension could then be registered in any app like so:
```typescript
export default defineWebApplication({
setup() {
const { extension } = useFileActionDownloadFiles()
return {
appInfo: {
name: $gettext('Download app'),
id: 'download-app'
},
extensions: computed(() => [unref(extension)])
}
}
})
```

View File

@@ -1,75 +0,0 @@
---
title: 'Application menu item extensions'
sidebar_position: 2
id: app-menu-item-extensions
---
## Extension Type AppMenuItem
This extension type allows apps to register links to internal or external pages within the application switcher menu on the top left.
### Configuration
The Interface for an `AppMenuItemExtension` looks like so:
```typescript
interface AppMenuItemExtension {
id: string
type: 'appMenuItem'
extensionPointIds?: string[]
label: () => string
color?: string
handler?: () => void
icon?: string
path?: string
priority?: number
url?: string
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
A `handler` will result in a `<button>` element. This is necessary when an action should be performed when clicking the menu item (e.g. opening a file editor).
A `path` will result in an `<a>` element that links to an internal page via the vue router. That means the given path needs to exist within the application.
A `url` will result in an `<a>` element that links to an external page. External pages always open in a new tab or window.
At least one of these properties has to be provided when registering an extension. If you define more than one, the priority order is `handler` > `path` > `url`.
`priority` specifies the order of the menu items. 50 is a good number to start with, then go up/down based on where the item should be placed. Defaults to the highest possible number, so the item will most likely end up at the bottom of the list if you don't specify a `priority`. Leave it empty if unsure what to pick.
## Example
The following example shows how an app creates an extension that registers an app menu item, linking to an internal page. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/main/packages/web-pkg) package.
```typescript
export default defineWebApplication({
setup() {
const { $gettext } = useGettext()
const appId = 'my-cool-app'
const menuItems = computed<AppMenuItemExtension[]>(() => [
{
id: 'com.github.opencloud-eu.web.my-cool-app.menu-item',
type: 'appMenuItem',
label: () => $gettext('My cool app'),
path: urlJoin(appId),
icon: 'star',
color: '#0D856F',
priority: 60
}
])
return {
appInfo: {
name: $gettext('My cool app'),
id: appId
},
extensions: menuItems
}
}
})
```

View File

@@ -1,49 +0,0 @@
---
title: 'Custom component extensions'
sidebar_position: 3
id: custom-component-extensions
---
## Extension Type CustomComponent
CustomComponent extensions need to define one or multiple `extensionPointId`s as render target. A `CustomComponentTarget` component for this very
extension point needs to be mounted in the current view.
### Configuration
To define a custom component extension, you implement the `CustomComponentExtension` interface.
Here's what it looks like:
```typescript
interface CustomComponentExtension {
id: string
type: 'customComponent'
extensionPointIds?: string[]
content: Slot | Component
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
The `content` property specifies a render function or a Component for the target extension point.
### Example
A simple example for a custom component extension could be a `NyanCat` progress bar component, being
targeted at the `global-progress-bar` extension point as render target.
```typescript
const extension = {
id: 'com.github.opencloud-eu.web.app.progress-bars.nyan-cat',
type: 'customComponent',
extensionPointIds: ['app.runtime.global-progress-bar'],
content: (slots) => [h(NyanCat, slots)],
userPreference: {
optionLabel: $gettext('Nyan Cat progress bar')
}
}
```
The `content` property in this example can also be defined as `content: NyanCat`.

View File

@@ -1,80 +0,0 @@
---
title: 'Folder view extensions'
sidebar_position: 4
id: folder-view-extensions
---
## Folder view extension type
The folder view is one of the possible extension types. Registered folder view can be used to render multiple resources (folders, files, spaces) in the UI.
### Configuration
This is what the FolderViewExtension interface looks like:
```typescript
interface FolderViewExtension {
id: string
type: 'folderView'
extensionPointIds?: string[]
folderView: FolderView // See FolderView section below
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
#### FolderView
For the folderView object, you have the following configuration options:
- `name` - The name of the action (not displayed in the UI)
- `label` - The text to be displayed to the user when switching between different FolderView options
- `icon` - Object, expecting an icon `name` and a corresponding `IconFillType`, see https://opencloud.design/#/Design%20Tokens/IconList for available options
- `isScrollable` - Optional boolean, determines whether the user can scroll inside the component or it statically fills the viewport
- `component` - The Vue component to render the resources. It should expect a prop of type `Resource[]`
- `componentAttrs` - Optional additional configuration for the component mentioned above
### Example
The following example shows how an extension for a custom folder view could look like. Note that the extension is wrapped inside a Vue composable so it can easily be reused. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/main/packages/web-pkg) package.
```typescript
export const useCustomFolderViewExtension = () => {
const { $gettext } = useGettext()
const extension = computed<FolderViewExtension>(() => ({
id: 'com.github.opencloud-eu.web.files.folder-view.custom',
type: 'folderView',
scopes: ['resource', 'space', 'favorite'],
folderView: {
name: 'custom-table',
label: $gettext('Switch to custom folder view'),
icon: {
name: 'menu-line',
fillType: 'none'
},
component: YourCustomFolderViewComponent
}
}))
return { extension }
}
```
The extension could then be registered in any app like so:
```typescript
export default defineWebApplication({
setup() {
const { extension } = useCustomFolderViewExtension()
return {
appInfo: {
name: $gettext('Custom folder view app'),
id: 'custom-folder-view-app'
},
extensions: computed(() => [unref(extension)])
}
}
})
```

View File

@@ -1,94 +0,0 @@
---
title: 'Left sidebar menu item extensions'
sidebar_position: 5
id: left-sidebar-menu-item-extensions
---
## Left sidebar menu item extension type
One possible extension type is left sidebar menu items. Registered left sidebar menu items get rendered in the left sidebar, as long as there is more than one available.
### Configuration
To define a left sidebar menu item, you implement the SidebarNavExtension interface.
It looks like this:
```typescript
interface SidebarNavExtension {
id: string
type: 'sidebarNav'
extensionPointIds?: string[]
navItem: AppNavigationItem // Please check the AppNavigationItem section below
}
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
#### AppNavigationItem
The most important configuration options are:
- `icon` - The icon to be displayed, can be picked from https://opencloud.design/#/Design%20Tokens/IconList
- `name` - The text to be displayed
- `route` - The string/route to navigate to, if the nav item should be a `<router-link>` (Mutually exclusive with `handler`)
- `handler` - The action to perform upon click, if the nav item should be a `<button>` (Mutually exclusive with `route`)
Please check the [`AppNavigationItem` type](https://github.com/opencloud-eu/web/blob/f069ce44919cde5d112c68a519d433e015a4a011/packages/web-pkg/src/apps/types.ts#L14) for a full list of configuration options.
### Example
The following example shows an extension that adds a left sidebar nav item inside the files app, linking to a custom page. Note that the extension is wrapped inside a Vue composable so it can easily be reused. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/main/packages/web-pkg) package.
```typescript
export const useCustomPageExtension = () => {
const { $gettext } = useGettext()
const extension = computed<SidebarNavExtension>(() => ({
id: 'com.github.opencloud-eu.web.files.left-nav.custom-page',
scopes: ['app.files'],
type: 'sidebarNav',
action: {
name: $gettext('Custom page'),
icon: 'world',
priority: 100,
isActive: () => true,
isVisible: () => true,
route: {
path: '/files/custom-page'
},
activeFor: [{ path: '/files/custom-page' }]
}
}))
return { extension }
}
```
The extension could then be registered in any app like so:
```typescript
export default defineWebApplication({
setup() {
const { extension } = useCustomPageExtension()
return {
appInfo: {
name: $gettext('Custom page app'),
id: 'custom-page-app'
},
routes: {
path: '/files/custom-page',
name: 'files-custom-page',
component: CustomPageComponent,
meta: {
title: $gettext('Custom Page')
}
},
extensions: computed(() => [unref(extension)])
}
}
})
```

View File

@@ -1,132 +0,0 @@
---
title: 'Right sidebar panel extensions'
sidebar_position: 6
id: right-sidebar-panel-extensions
---
## Extension Type SideBarPanel
The right sidebar is supposed to show information and make context specific actions available for single or multiple selected items.
It is structured in a hierarchical way:
- Panels which are defined as `root` panels get rendered as immediate members of the right sidebar.
- Panels which are defined as non-`root` panels receive a navigation item below the `root` panels so that users can navigate into the respective
sub panel.
### Configuration
To define a right sidebar panel extension, you implement the `SidebarPanelExtension` interface.
It can be found below:
```typescript
interface SidebarPanelExtension<R extends Item, P extends Item, T extends Item> {
id: string
type: 'sidebarPanel'
extensionPointIds?: string[]
panel: SideBarPanel<R, P, T> // Please check the SideBarPanel section below
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
The `panel` object configures the actual sidebar panel. It consists of different properties and functions, where all the functions get called with a
`SideBarPanelContext` entity from the integrating extension points.
#### SideBarPanelContext
```typescript
interface SideBarPanelContext<R extends Item, P extends Item, T extends Item> {
root?: R
parent?: P
items?: T[]
}
```
- `items` - The most important member of the panel context, which denotes all selected items. That can mean all selected files in a files listing,
all selected users in a user listing, the individual current file in a file editor.
- `parent` - The immediate parent of the selected items. For example, if the user selects a file in a file listing, the parent is the parent folder,
or if being in a root of a space, the space itself. Can be `null` for non-hierarchical contexts, e.g. a user listing.
- `root` - The uppermost parent of the selected items. For example, if the user selects a file in a file listing, the root is always the space in which
the selected files reside. Can be `null` for non-hierarchical contexts, e.g. a user listing.
#### SideBarPanel
```typescript
interface SideBarPanel<R extends Item, P extends Item, T extends Item> {
name: string
icon: string
iconFillType?: IconFillType
title(context: SideBarPanelContext<R, P, T>): string
isVisible(context: SideBarPanelContext<R, P, T>): boolean
component: Component
componentAttrs?(context: SideBarPanelContext<R, P, T>): any
isRoot?(context: SideBarPanelContext<R, P, T>): boolean
}
```
- `name` - A human readable id for the panel.
- `icon`, `iconFillType` and `title` - Properties which are used to render the panel itself or right sidebar navigation items for navigating into that panel.
- `isVisible` - Determines if the panel is available for the given panel context.
- `component` - Provides a component that renders the actual sidebar panel.
- `componentAttrs` - Defines additional props for the component with the given panel context.
- `isRoot` - Determines if the panel is a root panel for the given panel context.
## Extension Point FileSideBar
In the context of files (e.g. file listing, text editor for a single file, etc.) we have a dedicated component `FileSideBar` which can be
toggled (shown/hidden) with a button in the top bar. The component queries all extensions of the type `sideBarPanel` from the extension
registry that also fulfill the scope `resource`. By registering an custom extension of type `sideBarPanel` and scope `resource`, your extension
will automatically become available in all environments that display the `FileSideBar` (i.e. any file viewer, file editor, file listing).
## Example
The following example shows how a sidebar panel for displaying exif data for a resource could look like. Note that the extension is wrapped inside a Vue composable so it can easily be reused. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/main/packages/web-pkg) and the [web-client](https://github.com/opencloud-eu/web/tree/main/packages/web-client) packages.
```typescript
export const useExifDataPanelExtension = () => {
const { $gettext } = useGettext()
const extension = computed<SidebarPanelExtension<SpaceResource, Resource, Resource>>(() => ({
id: 'com.github.opencloud-eu.web.files.sidebar-panel.exif-data',
type: 'sidebarPanel',
scopes: ['resource'],
panel: {
name: 'exif-data',
icon: 'image',
title: () => $gettext('EXIF data'),
component: ExifDataPanelComponent,
isRoot: () => true,
isVisible: ({ items }) => {
if (items?.length !== 1) {
return false
}
return true
}
}
}))
return { extension }
}
```
The extension can then be registered in any app like so:
```typescript
export default defineWebApplication({
setup() {
const { extension } = useExifDataPanelExtension()
return {
appInfo: {
name: $gettext('Exif panel app'),
id: 'exif-panel-app'
},
extensions: computed(() => [unref(extension)])
}
}
})
```

View File

@@ -1,137 +0,0 @@
---
title: 'Search extensions'
sidebar_position: 7
id: search-extensions
---
## Search extensions
One possible extension type is search. Registered search extensions are available when using the search field in the topbar. A search extension can consist of a
`list` and a `preview` search. The result of a `preview` search is shown below the search input field while the `list` search result is
### Configuration
An example of a search extension configuration can be found below:
```typescript
interface SearchExtension {
id: string
type: 'search'
extensionPointIds?: string[]
searchProvider: {
id: string
available: boolean
displayName?: string
previewSearch?: SearchPreview // See SearchPreview section below
listSearch?: SearchList // See SearchList section below
}
}
```
For `id`, `type`, and `extensionPointIds`, please see [extension base section](./../index.md) in the top level docs.
The `searchProvider` object configures the actual provider. It consist of the following:
- `id` - Since your extension has an `id` and can only have one searchProvider, you can reuse the same value
- `available` - Can be used to programmatically disable/enable any searchProvider, e.g. by dynamically checking backend capabilities
- `displayName` - Optional, used to add a small hint to indicate the connection between search providers and their corresponding results
- `previewSearch` - See below
- `listSearch` - See below
#### ListSearch
The listSearch object consists of:
- `component` - Vue component that can render the values from the SearchResult below
- `search(term: string)` - Function that exectues the search, based on a given term. The term is formatted in [KQL](https://docs.opencloud.eu/services/search/#query-language). Please note that the returned values needs to be formatted to fit either `SearchResource` or `GenericSearchResultItem` type
#### PreviewSearch
The previewSearch object extends the listSearch with one additional attribute:
- `available` - Indicates whether a preview underneath the search bar is available for this search provider
## Example
The following example shows how a search extension that queries a Solr search engine could look like. Note that the extension is wrapped inside a Vue composable so it can easily be reused. All helper types and composables are being provided via the [web-pkg](https://github.com/opencloud-eu/web/tree/main/packages/web-pkg) and the [web-client](https://github.com/opencloud-eu/web/tree/main/packages/web-client) packages.
```typescript
export const useSolrSearchExtension = () => {
const { $gettext } = useGettext()
const previewSearch: SearchPreview = {
component: SolarSearchComponent, // see the next example snippet
available: () => true,
search: (term) => {
// actual search implementation
console.log('Querying solr search engine...')
}
}
const searchProvider: SearchProvider = {
id: 'solr-search',
available: true,
displayName: 'Solr Search',
previewSearch
}
const extension = computed<SearchExtension>(() => ({
id: 'com.github.opencloud-eu.web.solr-search',
type: 'search',
searchProvider
}))
return { extension }
}
```
The search component for the preview search container may look like this:
```Vue
<template>
<resource-list-item :resource="resource" />
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue'
import { Resource } from '@opencloud-eu/web-client'
import { SearchResultValue, ResourceListItem } from '@opencloud-eu/web-pkg'
export default defineComponent({
name: 'SolarSearchComponent',
components: { ResourceListItem },
props: {
searchResult: {
type: Object as PropType<SearchResultValue>,
default: () => ({})
}
},
setup(props) {
const resource = computed<Resource>(() => props.searchResult.data)
return { resource }
}
})
</script>
```
The extension can then be registered in any app like so:
```typescript
export default defineWebApplication({
setup() {
const { extension } = useSolrSearchExtension()
return {
appInfo: {
name: $gettext('Solr search app'),
id: 'solrs-search-app'
},
extensions: computed(() => [unref(extension)])
}
}
})
```

View File

@@ -1,4 +0,0 @@
{
"label": "Extension Types",
"position": 2
}

View File

@@ -1,6 +0,0 @@
---
title: 'Extension Types'
---
This section is a guide about the different predefined extension types of OpenCloud Web. Please refer to the respective
subpages to learn more about the individual extension types.

View File

@@ -1,4 +0,0 @@
{
"label": "Extension System",
"position": 5
}

View File

@@ -1,169 +0,0 @@
---
title: 'Extension system'
---
## Concepts and Building Blocks
OpenCloud Web can be extended through various entry points with custom **apps** and **extensions**.
### Distinction between Apps and Extensions
An Application in the context of OpenCloud Web is an artifact which can be installed in an OpenCloud instance.
It serves two main purposes:
1. It makes the full app viewport (everything below the top bar) available to the application developer for any custom
application code. This includes the ability to define views with routes, navigation items for the left sidebar, and more.
2. Through the `extensions` key in the application interface you can register extensions of any extension type. Those extensions
are then available in standardized extension points. Additionally, they can be queried from the extension registry for
your own purposes.
Both parts are optional. This means that an application can be a file editor without any custom extensions, or even contain
no custom application code at all and only host extensions to be registered in the extension registry, or a combination of both.
### Examples
You can find open source examples for apps and extensions in our [curated list of OpenCloud apps and extensions](https://github.com/opencloud-eu/awesome-apps).
Feel free to contribute or just be inspired for your own apps or extensions.
### Apps
To get started, define a `src/index.ts`. Below is the most basic example of its content:
```typescript
// Install '@opencloud-eu/web-pkg' as a devDependency first (only relevant for types and autocompletion, dependency is already provided by OpenCloud Web at runtime).
import {
AppWrapperRoute,
ApplicationFileExtension,
defineWebApplication
} from '@opencloud-eu/web-pkg'
export default defineWebApplication({
setup({ applicationConfig }) {
// Here, you have access to the full injection context, meaning you can use all composables that we provide via web-pkg
// Needs to be unique within all installed applications in any OpenCloud web instance
// Should be short, unique and expressive as it is used as prefix on all routes within your application
const appId = 'your-extension'
// See extensions section below
const extensions = [
...
]
// See details below
const navItems = [
...
]
// See details below
const routes = [
...
]
return {
appInfo: {
name: $gettext('Your application name'),
id: appId,
icon: 'aliens', // See https://opencloud.design/#/Design%20Tokens/IconList for available options
},
extensions,
navItems,
routes
}
}
})
```
By defining an application via `defineWebApplication` you can provide the following:
- `appInfo` - the application metadata, which is used to make the application available via the app switcher and the app registry.
- `navItems` - the statically defined navigation items for the left sidebar. Only gets rendered when more than 1 navigation item exists at runtime.
Additional dynamic navigation items can be registered via the extension registry.
- `routes` - the routes to the different views of your application. May be referenced within the `navItems`. Authentication requirements can be defined per item.
- `extensions` - the extensions to be registered in the extension registry. For more details see the `Extensions` section below.
If you want to learn how to implement an app for viewing and editing specific file types, please consult the [relevant documentation](./10-viewer-editor-apps.md) for detailed instructions and guidance.
To learn how to integrate an app into OpenCloud Web, please refer to the "Web Apps" section of the Web service docs ("Services" > "Web").
### Extensions
In contrast to applications, extensions usually have a rather small scope and dedicated functionality.
#### Extension Registry
The globally available extension registry provided by the OpenCloud Web runtime can be used to both register and query extensions. All extensions
which are being made available via an `app` get registered in the extension registry automatically. In your custom application code you can
then query any of the available extensions by providing an `extensionPoint` entity. Throughout the OpenCloud Web platform
and most prominently also in the `files` app we have defined some extension points which automatically use certain extensions, see the
`Extension Points` section below.
#### Extension Types
For building an extension you can choose from the types predefined by the OpenCloud Web extension system. See the full list of available extension types below.
1. `ActionExtension` (type `action`) - An extension that can register `Action` items which then get shown in various places (e.g. context menus, batch actions), depending on the
extension points referenced in the extension respectively. Most commonly used for file and folder actions (e.g. copy, rename, delete, etc.). For details, please refer to the [action docs](./20-extension-types/10-action-extensions.md)
2. `SearchExtension` (type `search`) - An extension that can register additional search providers. For details, please refer to the [search docs](./20-extension-types/70-search-extensions.md).
3. `SidebarNavExtension` (type `sidebarNav`) - An extension that can register additional navigation items for the left sidebar. These can be scoped to specific apps, and programmatically enabled/disabled.
For details, please refer to the [sidebar nav docs](./20-extension-types/50-left-sidebar-menu-item-extensions.md).
4. `SidebarPanelExtension`, (type `sidebarPanel`) - An extension that can register panels for the right sidebar. For details, please refer to the [sidebar panel docs](./20-extension-types/60-right-sidebar-panel-extensions.md).
5. `FolderViewExtension` (type `folderView`) - An extension that can register additional ways of displaying the content of a folder (resources like spaces, folders or files) to the user.
For details, please refer to the [folder view docs](./20-extension-types/40-folder-view-extensions.md).
6. `CustomComponentExtension` (type `customComponent`) - An extension that can register a custom component for a render target. For details, please refer to the
[custom component docs](./20-extension-types/30-custom-component-extensions.md)
You're free to introduce your own extension types within your application code and use the extension registry to query the available ones. However, if you have the impression
that an important extension type is missing and would be beneficial for the platform, please reach out to us by opening a [GitHub issue](https://github.com/opencloud-eu/web/issues/new/choose).
#### Extension Base Configuration
Any extension is required to define at least an `id` and a `type` in order to fulfill the generic `Extension` interface.
The `id` is supposed to be unique throughout the OpenCloud Web ecosystem. In order to keep `id`s readable for humans we didn't want to enforce uniqueness through e.g. uuids.
Instead, we chose to use dot-formatted namespaces like e.g. `com.github.opencloud-eu.web.files.search`. We'd like to encourage you to follow the same format for your own extensions.
For the `type` you can choose from the ones listed above or define a custom one.
In addition, you can also pass optional `extensionPointIds` to further limit the usage of an extension. With the right click context menu and the batch actions being
two different extension points, this could mean that a file action extension is only allowed in the context menu, but not in the batch actions.
You can find predefined extension point ids in the extension points section below.
#### Extension Points
There are standardized components and places where extensions are being used automatically. The following ones are currently provided by the OpenCloud Web runtime or
the `files` app. If you decide to develop an extension which fulfills the type and registers itself for the extensionPointId of the respective extension point,
your extension will be used automatically.
1. Left Sidebar for Navigation. ExtensionPointId `app.${appName}.navItems` (dynamically created for each app). Mounts extensions of type `sidebarNav`.
2. Global top bar
1. Center area. ExtensionPointId `app.runtime.header.center`. Mounts extensions of type `customComponent`.
2. Progress bar for the global loading state. ExtensionPointId `app.runtime.global-progress-bar`. Mounts a single extensions of type `customComponent`. If multiple exist, the user can choose via the account page.
3. Files app
1. Right sidebar. ExtensionPointId `app.files.sidebar`. Mounts extensions of type `sidebarPanel`. Used in any file(s) context (files app, file viewer apps, file editor apps).
2. Folder views for regular folders. ExtensionPointId `app.files.folder-views.folder`. Mounts extensions of type `folderView`.
3. Folder views for the project spaces overview. ExtensionPointId `app.files.folder-views.project-spaces`. Mounts extensions of type `folderView`.
4. Folder views for the favorites page. ExtensionPointId `app.files.folder-views.favorites`. Mounts extensions of type `folderView`.
5. Right click context menu. ExtensionPointId `global.files.context-actions`. Mounts extensions of type `action`.
6. Batch actions in the app bar above file lists. ExtensionPointId `global.files.batch-actions`. Mounts extensions of type `action`.
7. Default actions (left click) on a file. ExtensionPointId `global.files.default-actions`. Mounts extensions of type `action`.
8. Upload menu. ExtensionPointId `app.files.upload-menu`. Mounts extensions of type `action`.
9. Quick actions. ExtensionPointId `app.files.quick-actions`. Mounts extensions of type `action`.
4. Global search providers. ExtensionPointId `app.search.providers`. Utilizes extensions of type `search` as search engines for the search input in the global top bar.
5. User preference panels. ExtensionPointId `app.runtime.preferences.panels`. Mounts extensions of type `customComponent`.
#### User Preferences for Extensions
To allow users to configure extensions, extension points can define user preferences. User preferences are defined as an object on the extension point configuration.
Whenever an extension point declares to accept user preferences, it will get listed with a dropdown on the Preferences page (reachable via top right user menu).
The user can then select one out of all the extensions which have been registered for this extension point.
### Helpful packages
We currently offer the following packages that can be integrated into your app, providing useful utilities and types.
- `web-client` - This package serves as an abstraction layer between the server APIs and an app or extension. It converts raw API data into objects with helpful types and utilities. For details, please refer to the package's [README.md](https://github.com/opencloud-eu/web/blob/main/packages/web-client/README.md).
- `web-pkg` - This package provides utilities, most importantly a variety of components and composables, that can be useful when developing apps and extensions. For details, please refer to the package's [README.md](https://github.com/opencloud-eu/web/blob/main/packages/web-pkg/README.md).

View File

@@ -1,4 +0,0 @@
{
"label": "Web",
"position": 2
}

View File

@@ -1,6 +0,0 @@
---
title: 'Web UI'
---
This is the next generation OpenCloud frontend.
If you're new here, head over to the the [getting started guide](./10-getting-started.md) for a quick introduction.

View File

@@ -1,17 +0,0 @@
---
sidebar_position: 1
id: intro
title: Welcome
custom_edit_url: https://github.com/opencloud-eu/opencloud/edit/main/docs/intro.md
---
# Welcome
Welcome to the OpenCloud Developer Documentation.
Please be patient, we are working on the content.
If you want to contribute to the dev docs, please visit [OpenCloud on Github](https://github.com/opencloud-eu/).
Contents will be transferred during the build process.