Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions docs/dev/web/development/_category_.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
{
"label": "Development",
"position": 2,
"link": {
"type": "generated-index",
"description": "\uD83D\uDC69\u200D\uD83D\uDCBB Development",
"slug": "dev/web/development/"
}
"position": 2
}
9 changes: 9 additions & 0 deletions docs/dev/web/development/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: 'Development'
---

This section covers the development workflow and conventions for contributing to OpenCloud Web.

- [Tooling](./tooling) describes the local development setup and the tools used to build OpenCloud Web.
- [Repository structure and published packages](./repo-structure) explains the most important parts of the Web repository.
- [Conventions](./conventions) summarizes the coding and contribution conventions used by the project.
4 changes: 4 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Advanced topics",
"position": 3
}
57 changes: 57 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: 'Configuration'
sidebar_position: 1
---

This page describes how to add configuration options to your app. Administrators can then change the behaviour of your
app.

## Manifest and defaults

Put static metadata of your app into `src/manifest.json`. It ends up in the `manifest.json` of your build, together with
the `name`, `version`, `description`, `license` and `author` fields of your `package.json`, and the entry point of your
app.

Default values for the app config belong under the `config` key:

```json title="src/manifest.json"
{
"config": {
"maxFileSize": 10485760,
"showPreview": true
}
}
```

## Overriding values

There are two ways to override the defaults:

- During development, put your values into `src/config.json`. The file is merged on top of the `config` key of
`src/manifest.json`. Don't commit values that only apply to your machine.
- In a real deployment, administrators override them via the `apps.yaml` file, see the
[web applications admin docs](../../../../admin/configuration/web-applications).

```json title="src/config.json"
{
"showPreview": false
}
```

## Reading values in your app

The merged config is passed to the `setup` function of your app definition as `applicationConfig`:

```typescript title="src/index.ts"
import { defineWebApplication } from '@opencloud-eu/web-pkg';

export default defineWebApplication({
setup({ applicationConfig }) {
const showPreview = applicationConfig?.showPreview ?? true;

// ...
}
});
```

Always provide a fallback value. The config can be empty, and an administrator can set any value.
10 changes: 10 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: 'Advanced topics'
---

This section covers additional concepts for developing and maintaining OpenCloud Web apps.

- [Configuration](./configuration) explains how to define defaults and expose configuration options to administrators.
- [Styling](./styling) describes how to use the OpenCloud design system, color roles, and Tailwind CSS.
- [Translations](./translations) explains how to make an app available in multiple languages.
- [Testing](./testing) introduces the available test setup and helpers.
59 changes: 59 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/styling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: 'Styling'
sidebar_position: 2
---

This page describes how to style your app so that it fits into OpenCloud Web.

## Design system components

The `@opencloud-eu/design-system` package contains the components that OpenCloud Web is built with, for example buttons,
inputs and modals. Use them where possible. Your app then follows the platform look and inherits accessibility and
theming for free. See the [design system documentation](../../design-system) for the list of components.

## Color roles

OpenCloud Web exposes its colors as CSS variables with the `--oc-role-` prefix, for example `--oc-role-surface` or
`--oc-role-on-surface`. Always use these variables instead of fixed color values. They change with the active theme and
with the color scheme, so your app stays readable in light and dark mode.

```css
.my-panel {
background-color: var(--oc-role-surface-container);
color: var(--oc-role-on-surface);
}
```

See [the defaults](https://github.com/opencloud-eu/web/blob/main/packages/design-system/src/styles/defaults.css) for a
complete list of color roles.

## Tailwind CSS

The `@opencloud-eu/extension-sdk` package ships a preconfigured [Tailwind CSS](https://tailwindcss.com/) setup. Import
its stylesheet in your entrypoint (typically `src/index.ts`) to use it:

```typescript title="src/index.ts"
import '@opencloud-eu/extension-sdk/tailwind.css';
```

Tailwind classes need the `ext:` prefix in apps. The prefix avoids style conflicts with the OpenCloud Web runtime.

```html
<main class="ext:p-4 ext:flex ext:gap-2">...</main>
```

The setup maps the color roles to Tailwind color utilities, so `--oc-role-surface` is available as `bg-role-surface`,
`text-role-on-surface` and so on:

```html
<div class="ext:bg-role-surface-container ext:text-role-on-surface">...</div>
```

It also sets the OpenCloud font family, a spacing unit of `4px`, the breakpoints `xs`, `sm`, `md`, `lg` and `xl`, and a
`dark` variant. The `dark` variant follows the color scheme of OpenCloud Web, not the browser setting:

```html
<div class="ext:bg-white ext:dark:bg-black">...</div>
```

In most cases you do not need the `dark` variant, because the color roles already handle both color schemes.
41 changes: 41 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
title: 'Testing'
sidebar_position: 4
---

This page describes how to test your app.

## Unit tests

The [web-app-skeleton repository](https://github.com/opencloud-eu/web-app-skeleton) comes with a
[vitest](https://vitest.dev/) setup. Run the unit tests with:

```bash
pnpm test:unit
```

## Test helpers

The `@opencloud-eu/web-test-helpers` package provides utilities for mounting components with a mocked OpenCloud Web
context. Without them, every component that uses a composable of `web-pkg` fails to mount.

```typescript title="tests/unit/App.spec.ts"
import { defaultPlugins, mount } from '@opencloud-eu/web-test-helpers';
import App from '../../src/App.vue';

describe('App', () => {
it('renders the title', () => {
const wrapper = mount(App, { global: { plugins: [...defaultPlugins()] } });
expect(wrapper.text()).toContain('My app');
});
});
```

For details, please refer to the package's
[README.md](https://github.com/opencloud-eu/web/blob/main/packages/web-test-helpers/README.md).

## End to end tests

For end to end tests with [Playwright](https://playwright.dev/), the
[web](https://github.com/opencloud-eu/web) and [web-extensions](https://github.com/opencloud-eu/web-extensions)
repositories contain working examples.
138 changes: 138 additions & 0 deletions docs/dev/web/extension-system/advanced-topics/translations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: 'Translations'
sidebar_position: 3
---

OpenCloud Web uses [gettext](https://www.gnu.org/software/gettext/) for translations, via the
[vue3-gettext](https://jshmrtn.github.io/vue3-gettext/) library. Your app brings its own translations and hands them to
the Web runtime. The language that the user picked in OpenCloud is then also used for your app.

Translations are optional. An app without them simply shows its original strings.

## Marking strings

In a script or a composable, get `$gettext` from `useGettext`:

```typescript
import { useGettext } from 'vue3-gettext';

const { $gettext } = useGettext();

const title = $gettext('Your application name');
```

In a template, `$gettext` is globally available:

```html
<template>
<p>{{ $gettext('No files here') }}</p>
</template>
```

Pass variables as an object. Use the `%{name}` syntax in the string, never string concatenation, because the word order
changes between languages:

```typescript
$gettext('Open «%{resource}»', { resource: resource.name });
```

For plurals, use `$ngettext` with the singular form, the plural form, the count, and the variables:

```typescript
const { $ngettext } = useGettext();

$ngettext(
Comment thread
JammingBen marked this conversation as resolved.
'Delete the selected resource?',
'Delete %{amount} selected resources?',
resources.length,
{ amount: resources.length.toString() }
);
```

:::warning
The extraction tool reads your source code, not your runtime values. Always pass a plain string literal to `$gettext`.
A variable or a template literal cannot be extracted.
:::

## Setting up the workflow

Add [vue3-gettext](https://jshmrtn.github.io/vue3-gettext/) as a dev-dependency. It ships the `vue-gettext-extract` and
`vue-gettext-compile` commands.

Create a `gettext.config.cjs` in the root of your app. It defines which files are scanned and which languages you
support:

```javascript title="gettext.config.cjs"
module.exports = {
input: {
path: './src',
include: ['**/*.js', '**/*.ts', '**/*.vue']
},
output: {
locales: ['de', 'es', 'fr', 'it'],
path: './l10n/locale',
potPath: '../template.pot',
jsonPath: '../translations.json',
flat: false,
linguas: false
}
};
```

Add two scripts to your `package.json`:

```json title="package.json"
{
"scripts": {
"l10n:extract": "vue-gettext-extract",
"l10n:compile": "vue-gettext-compile"
}
}
```

`l10n:extract` collects all marked strings into `l10n/template.pot` and creates one `.po` file per language under
`l10n/locale`. Run it whenever you add or change a string.

`l10n:compile` turns the `.po` files into a single `l10n/translations.json`. Run it after the translated `.po` files
come back. Commit `translations.json`, because the build imports it.

How the `.po` files get translated is up to you. The OpenCloud repositories use
[Transifex](https://www.transifex.com/), but any gettext based service or a manual workflow works as well.

## Registering translations

Import `translations.json` and return it from your app definition. The Web runtime merges it into the global
translations when your app is loaded.

```typescript title="src/index.ts"
import { defineWebApplication } from '@opencloud-eu/web-pkg';
import { useGettext } from 'vue3-gettext';
import translations from '../l10n/translations.json';

export default defineWebApplication({
setup() {
const { $gettext } = useGettext();

return {
appInfo: {
name: $gettext('Your application name'),
id: 'your-app'
},
translations
};
}
});
```

The file is a map of language key to message map:

```json title="l10n/translations.json"
{
"de": {
"No files here": "Keine Dateien hier"
},
"fr": {
"No files here": "Aucun fichier ici"
}
}
```
Loading
Loading