Skip to content

Commit

Permalink
pkp/pkp-lib#9527 Documentation WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
jardakotesovec committed Jan 29, 2024
1 parent faf27b0 commit d1d0805
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 68 deletions.
11 changes: 11 additions & 0 deletions src/docs/guide/APIInteractions.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/API interactions" />

OJS/OMP/OPS already have substantial [Rest API interface](https://docs.pkp.sfu.ca/dev/api/ojs/3.4) well covered with documentation.

To interact with the API there are handy composables to simplify the process.

## Compose URL

First its necessary to generate correct API URL. For that use
45 changes: 45 additions & 0 deletions src/docs/guide/CompositionAPI.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/Vue Composition API" />

# Vue Composition API

In version 3.5 we migrated our ui-library to new Vue3 version, which introduces new [Composition API](https://vuejs.org/guide/introduction.html#composition-api) as more flexible alternative to existing Options API.

## Composition API

Composition API builds on exactly same principles as the Options API that we are used to from Vue 2. State management still consist of **state** , **computed** properties and **methods** that are updating the **state**. Only the syntax is different.

It might take bit getting used to to new syntax, but it will reward us is with significantly better flexibility compared to mixins, which comes very handy in complex application like OJS/OPS/OMP.

Best learning source is Vue.js [documentation](https://vuejs.org/guide/introduction.html), where you can easily explore syntax differences between options API and composition API.

We follow Vue.js [recommendation](https://vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive) to use exclusively `ref` to define reactive state and avoiding `reactive` API due its limits.

## Composables

Composables are replacements for mixins. More details to come about the composables that we provide instead of existing mixins. In general these will be useful to handle all common tasks, like interacting with API, translation or manupulating forms.

## How to organise the code

Even though it seems that Composition API is lacking organizational structure, it actually gives possibility of organise the code by individual features. Every feature usually consist of some state, computed properties and methods. Good example is `SubmissionsPageStore.js`.

Also it should be easy to quickly understand how the Page works when reading the business logic in store. If its too long or/and contains long functions its indication that some of the features should be moved to individual feature composables. These than can be imported to the store and connected with rest of the features in the store. This helps to get good understanding how things works together and its always possible to dive in into details in individual composables if needed.

## Refs vs reactive

If you want to dive deep and understand all details between using `ref()` vs `reactive()` when creating reactive state in Composable API, you can check out [official documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals) or detailed [blog post](https://mokkapps.de/blog/ref-vs-reactive-what-to-choose-using-vue-3-composition-api).

But its not really necessary, as we follow [official recommendation](https://vuejs.org/guide/essentials/reactivity-fundamentals#limitations-of-reactive), which aligns with my personal experience as well, which is just always use `ref()` when defining state..

```javascript
import {ref} from 'vue';

// defining state
const isLoading = ref(false);
const items = ref([]);

// modifying the state, using the .value
items.value.push({title: 'a'});
isLoading.value = true;
```
38 changes: 33 additions & 5 deletions src/docs/guide/PageArchitecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,37 @@ import {Meta} from '@storybook/blocks';

These principles applies for version 3.5 and higher when building new pages or making significant changes to existing ones.

- Using [pinia store](https://pinia.vuejs.org) for state management. Check [vue3 guide](..?path=/docs/guide-vue3--docs#pinia-store) for more details.
- Page and page related components are located in `src/pages` folder
- Not using Smarty template for page itself, template is no part of the vue.js page component. Check out [this guide](../?path=/docs/guide-smarty-vs-vue-js--docs) for more details.
- For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).
Since its recent change, we have limited number of examples in our code base. At this point best to follow [Example Page](?path=/docs/pages-example--docs) and [New Submission Page](?path=/docs/pages-submissions--docs). There is more to come soon.

Please checkout [Example Page](../?path=/docs/pages-example-examplepage--docs), which provides well documented boilerplate.
## Folder structure

All Page related files are localed in `lib/ui-library/src/pages` folder. Check out [ExamplePage](?path=/docs/pages-example--docs#folder-structure) for per file breakdown.

## Page template included in Page component.

To [simplify](?path=/docs/guide-technical-roadmap--docs#vuejs--smarty---vuejs-35) the developer experience when creating Pages, we are moving from combination of Smarty and Vue.js to use pure Vue.js components, which have templates as part of the component file.

## State management

For managing Page business logic we are leveraging Pinia store. Main reason why we want to use Pinia stores instead of just managing state directly in the Page component, is that behind the scene its very extensible and plugin friendly. You can check out more detailed break down in our [Technical Roadmap](?path=/docs/guide-technical-roadmap--docs#pinia-stores-35). General UI components still contains interaction logic directly in component.

To understand how the Components should interact with the **Component Pinia Store**, which is our simple extension of the Pinia Store, check out dedicated [page](http://localhost:6006/?path=/docs/guide-pinia-store--docs#component-pinia-store) for Pinia store.

### Where to place business logic?

- **Page**: Every Page will have its own [Component Pinia Store](?path=/docs/guide-pinia-store--docs#component-pinia-store), where most of the business logic is handled and for simpler Pages that should be good enough.
- **Complex Modals**: If Page contain Modal(s) that contain substantial functionality, which is reasonably well separable from the rest of the Page, than its good indication that the Modal should have its own Component Store. Good example is `SubmissionSummaryModal.vue`.
- **Self-Contained Component**: We don't have good example yet of Self-Contained Component, which would have its own Pinia store. But good candidates in general are Components that can be used on multiple places, and their logic can be well separated from the rest of the page. Like managing Submission files in both Submission wizard and Workflow editor.

## Server side configuration

On initial page load, there is still opportunity to pass JS object from PHP to the Vue.js. Best is to express individual items as props, so it can be easily displayed in storybook and its well documented.

This might include things like:

- Form structure configuration, as we do configure forms on PHP side
- Any configuration which needs to be passed just once on page load

## Styling

For styling use TailwindCSS, for more details check out [styling guide](../?path=/docs/guide-style-introduction--docs).
68 changes: 68 additions & 0 deletions src/docs/guide/PiniaStores.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/Pinia Store" />

# Pinia Store

For more details, why Pinia store is being adopted from 3.5, check out our [Technical Roadmap](?path=/docs/guide-technical-roadmap--docs#pinia-stores-35)

## Pinia Store Gotchas

Intentional as first chapter to safe you some time.

When destructing state from pinia store, it requires to use helpers to not loose reactivity. Alternatively using the store directly without destructing is also good practice. Check example in [Pinia documentation](https://pinia.vuejs.org/core-concepts/#Destructuring-from-a-Store).

## Component Pinia Store

Purpose of **Component Pinia Stores** is to contain business logic for individual **Pages**, **Complex Modals** and **Self-Contained Components**. Read more details, where to place business logic in [Page architecture](?path=/docs/guide-page-architecture--docs#where-to-place-business-logic).

Compared to general pinia store, its extended with following features:

- **Passing initial data**: It allows to pass inititial data when needed. That often might be configuration passed to the **Page** from PHP backend. Or some props being passed to **Complex Modal** or **Self-Contained Component**. Important to note that only the first component (its top in hirearchy) is passing the configuration. Any other child component thats connecting to the store connects to it without passing the init state as its already been initialised.
- **Following Component Lifecycle**: It gets initialised when its called in first component. It tracks mount/unmount events and when all components using the store are unmounted it deletes its state. This is particularly useful when managing **Complex Modal** as anytime its closed - its state will be removed and it gets initialised next time the modal is opened with new props.

These improvements are intended to make the developer experience very much the same as if we would define the business logic directly in the component. But still getting [benefits](<(?path=/docs/guide-technical-roadmap--docs#pinia-stores-35)>) that comes from using Pinia store.

Lets look at example how the component interacts with the store. More advanced example can be found in [ExamplePage](?path=/docs/pages-example--docs).

```javascript
import {ref, computed} from 'vue';
import {defineComponentStore} from '@/utils/defineComponentStore';

export const useExamplePageStore = defineComponentStore(
'examplePage',
(props) => {
const itemsPublishedCount = computed(() => {
return props.items.filter((item) => item.status === 'published').length;
});

return {
items: props.items,
itemsPublishedCount,
};
},
);
```

```html
<template>
<span>Published count: {{ store.itemsPublishedCount }}</span>
<ul>
<li v-for="item in store.items">{{ item.title}}</li>
</ul>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
required: true,
},
});
// when
const store = useExamplePageStore(props);
</script>
```

## General Pinia Store

Couple of Pinia stores are also used for internal purposes, like managing announcements or dialogs. At this point these are not intended to interact with directly. There are dedicated composables for interacting with dialogs for example. Store purpose in these cases it to manage some global state.
15 changes: 0 additions & 15 deletions src/docs/guide/SmartyVsVue.mdx

This file was deleted.

107 changes: 107 additions & 0 deletions src/docs/guide/TechnicalRoadmap.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {Meta} from '@storybook/blocks';

<Meta title="Guide/Technical Roadmap" />

# Admin Frontend Technical Roadmap

## Introduction

Modern web is evolving rapidly. And long term projects like OJS/OPS/OMP has to be creative in tackling technical debt, so it can keep delivering new/improved functionality, while modernising underneath.

As title suggests, goal is to cover technical roadmap for the admin interface. Later it will be expanded also by plans for reader interface, where the considerations might differ.

## Smarty

Before we focus on future plans, lets remind where we started to better understand the journey.

It all started as typical server side rendered Smarty templates with custom controllers, where jQuery was main tool to handle user interactivity.

And there are still significant portion of the interface driven by this stack.

## Smarty -> Vue.js + Smarty (3.3??)

Around year 2018 team decided to start leveraging Vue.js framework. At that time it was clear that frameworks like React or Vue.js are much more capable in handling complex user interactions compared to jQuery.

Big benefit of Vue.js is that its templating system is fully compatible with html. That significantly helped with gradual adoption.

From technical point of view Smarty template gets rendered on server first, and than Vue.js framework uses the resulted html mark up as its own template in the browser. As result it allows to combine functionality of both systems and helps with gradual transition, where some things were easier to do in Smarty (server side) and some in Vue.js (client side).

## Vue.js + Smarty -> Vue.js (3.5)

During the 3.5 development cycle we decided to basically double down on Vue.js and stop mixing it with Smarty server side rendering.

Even though it was significant benefit to keep Smarty around at first. It also comes with some challenges, here is list of factors that influenced the decision:

- Combining Smarty and Vue.js syntax requires to be familiar with both of them and some Vue.js syntax needs to be escaped to work within Smarty template.
- Different content escaping mechanism, which makes easier to make mistake (XSS attacks).
- Developer has to decide what to render in Smarty and what in Vue.js.
- Difficult to develop and test pages in separation (Storybook).
- Sometime difficult to identify, which Smarty template is interpreted by which Vue.js Page component, as these lives in different folder structure.
- Security risk with injecting Vue.js syntax to Smarty template.
- There is relatively small performance penalty, when Vue.js is compiling the template runtime in browser (which was required to be able to consume smarty output), rather than in build time.

Overall idea is that embracing client side rendering and exchanging only JSON data with backend should make overall architecture easier to grasp and maintain.

There is only couple of things, where Vue.js was lacking compared to Smarty, main being ability to easy do the translations in Vue.js and being able to extend UI from plugins. These will be covered below.

### Migration path

Having consistent Page architecture would make significantly easier to orient everyone and migrating from smarty templates that are used for Pages should be relatively low effort. We will aim to execute some migrations during 3.5 and 3.6 dev cycle.

## Translation (3.5)

We are moving from passing translations via Props to individual components to system, where all uses of translation keys are automatically identified and needed translations are exposed to Vue.js components.

For more details check Translation guide.

### Migration path

It would be benefitial if we would move all (non-dynamic) translations from props to `t()` calls, because than we can render our components and pages in storybook in different languages with little effort. Also no need to duplicate translations in storybook and po files. Migrating should be relatively low effort. Therefore make sense to schedule migration in 3.5 or 3.6 dev cycle.

## Extensibility (3.5)

Details are being worked out and will be covered in detail bit later in 3.5 development cycle. But in principle it will be combination of possibility to register own Vue.js component ([introduced in 3.4](https://github.com/jardakotesovec/backend-ui-example-plugin)) from plugin and than render it on specific place in interface (details TBD). This will replace mechanism where Smarty template was extended via hooks. We intend to provide backward compatibility layer to give plugins enough time to adapt.

## Vue3 Composition API (3.5)

Upgrading our ui library to Vue3 opened new posibilities. Vue3 introduced composition API, which is now recommended API. Even though its very much the same as Option API under the hood the syntax is significantly different and might be intimidating first. Eventually we decided to adopt it and here are some factors that encouraged that decision.

- Some of our admin interfaces are quite complex and composition API makes easier to split these to smaller features and than combine them as needed.
- Composition API works lot better with type systems, like Typescript is. We have not decided to adopt such type system, but its good to have these doors opened in future.
- Even though Options API will be supported long term, Vue.js community is leaning towards new composition API. As result in future there likely will be stronger ecosystem and documentation for composition API.
- Composition API introduced composables as replacement for mixins that are lot more flexible and easier to test. You can find several composables in our storybook already that should make easier to build new functionality.

### Migration path

Options API should be supported very long term therefore migrating individual component/page to Composition API is usually encouraged only when doing significant changes or creating new one.

## Pinia stores (3.5)

Business logic has been mostly placed within top component, which we usually refer as **Page** component. And that works great, only difference we want to do is that we now connect the **Page** component to its own [Pinia store](https://pinia.vuejs.org) and use the same Composition API as we would use directly in component.

And just from moving the business logic from page component to its store we get some important benefits:

- Pinia store is well [extensible](https://pinia.vuejs.org/core-concepts/plugins.html) out of the box. That could be useful for plugins if they need to intercept/amend state changes or method calls for custom behaviour.
- Its easier for Vue component coming from plugin to connect to the store and interact with the data as needed, because it does not require explicit passing of props.
- Vue.js developers tool also shows all registered pinia store and their state & methods, which makes easier to explore whats available to work with.
- For more complex Pages it might be better to break them down to multiple components so its easier to understand the layout, and all of them can connect to the same store.
- Pinia is part of official Vue ecosystem, which should ensure long support.

### Migration path

Given that there are some limitations when using Options API in Pinia (like missing watch), it requires to migrate also Options API to Composition API for given Page, which is not trivial. Therefore more complex pages make sense to migrate only if there are some significant changes being done anyway or if the page is quite simple. We might schedule some simple migrations during dev cycle so we eventually have consistent Page architecture.

## TailwindCSS (3.5)

We have been also revisiting our approach to CSS styling. Having now UI/UX designer on the team is pushing our interfaces to new hights. And having CSS framework which is good in capturing design system became essential.

Here are the factors that encouraged us to adopt TailwindCSS at the end:

- TailwindCSS is created with "good design" in mind. Out of the box provides good subset of CSS styling that "looks good". Good example are [sizing scales](https://tailwindcss.com/docs/width) and [spacing scales](https://tailwindcss.com/docs/padding).
- Its easy to adjust for our custom fonts, colors, borders, shadows just via configuration.
- No need to enforce class naming convention as its not needed.
- While migrating to vue3, we picked [headless-ui](https://headlessui.com) library as best option to handle more complex components (combobox, modal,...) with good accessibility and its specifically built to be easily styled by TailwindCSS.

### Migration path

- Main motivation for TailwindCSS is to make it easier to style new interfaces according to our design system. There will be couple of cases where migrating existing basic components might be necessary to fit well to our new designs. But overall we want to use it for significant refactors or new components.
Loading

0 comments on commit d1d0805

Please sign in to comment.