QPANC - Parte 10 - Quasar - Configurações e Customizações
Tags: quasar, postgres, aspnetcore, docker
Published: 2020-04-19 23:37:15

> **QPANC** são as iniciais de **Q**uasar **P**ostgreSQL **A**SP **N**ET **C**ore.

  * [Source](https://github.com/TobyMosque/qpanc)
  * [Introdução](https://dev.to/quasar-pt-br/arquiteturando-uma-aplicacao-com-quasar-postgresql-asp-net-core-e-docker-introducao-o7o)
  * [Parte I - ASP.NET - Inicializando os Projetos](https://dev.to/quasar-pt-br/qpanc-parte-1-asp-net-inicializando-os-projetos-lkm)
  * [Parte 2 - PostgreSQL](https://dev.to/quasar-pt-br/qpanc-parte-2-postgresql-51pb)
  * [Parte 3 - ASP.NET - Registrando Serviços e Lendo Variáveis de Ambiente](https://dev.to/quasar-pt-br/qpanc-parte-3-asp-net-registrando-servicos-e-lendo-variaveis-de-ambiente-3do2)
  * [Parte 4 - ASP.NET - Entity Framework e ASP.NET Core Identity](https://dev.to/quasar-pt-br/qpanc-parte-4-asp-net-entity-framework-e-asp-net-core-identity-40f9)
  * [Parte 5 - ASP.NET - Documentação Interativa com Swagger](https://dev.to/quasar-pt-br/qpanc-parte-5-asp-net-documentacao-interativa-com-swagger-4n0o)
  * [Parte 6 - ASP.NET - Regionalização](https://dev.to/quasar-pt-br/qpanc-parte-6-asp-net-regionalizacao-13p5)
  * [Parte 7 - ASP.NET - Autenticação e Autorização](https://dev.to/quasar-pt-br/qpanc-parte-7-asp-net-autenticacao-e-autorizacao-bpd)
  * [Parte 8 - ASP.NET - CORS](https://dev.to/quasar-pt-br/qpanc-parte-8-asp-net-cors-52dn)
  * [Parte 9 - Quasar - Criação e Configuração do Projeto](https://dev.to/quasar-pt-br/qpanc-parte-9-quasar-criacao-e-configuracao-do-projeto-5hbi)
  * [Parte 10 - Quasar - Configurações e Customizações](https://dev.to/quasar-pt-br/qpanc-parte-10-quasar-configuracoes-e-customizacoes-2i86)
  * [Parte 11 - Quasar - Componentes - Diferença entre SPA e SSR](https://dev.to/quasar-pt-br/qpanc-parte-11-quasar-componentes-diferenca-entre-spa-e-ssr-1a3c)
  * [Parte 12 - Quasar - Serviços](https://dev.to/quasar-pt-br/qpanc-parte-12-quasar-servicos-4e61)
  * [Parte 13 - Quasar - Regionalização e Stores](https://dev.to/quasar-pt-br/qpanc-parte-13-quasar-regionalizacao-e-stores-1kep)
  * [Parte 14 - Quasar - Consumindo a API](https://dev.to/quasar-pt-br/qpanc-parte-14-quasar-consumindo-a-api-fn6)
  * [Parte 15 - Quasar - Login](https://dev.to/quasar-pt-br/qpanc-parte-15-quasar-login-jc6)
  * [Parte 16 - Quasar - Áreas Protegidas](https://dev.to/quasar-pt-br/qpanc-parte-16-quasar-areas-protegidas-3jkn)
  * [Parte 17 - Quasar - Registro](https://dev.to/quasar-pt-br/qpanc-parte-17-quasar-registro-3b8c)
  * [Parte 18 - Docker - Maquina Virtual Linux](https://dev.to/quasar-pt-br/qpanc-parte-18-docker-maquina-virtual-linux-g5c)
  * [Parte 19 - Docker - Registro e Build](https://dev.to/quasar-pt-br/qpanc-parte-19-docker-registro-e-build-31i4)
  * [Parte 20 - Docker - Traefik e Publicação](https://dev.to/quasar-pt-br/qpanc-parte-20-docker-traefik-e-publicacao-3g3d)
  * [Demo Online](https://qpanc.tobiasmesquita.dev)

##18 Customização de Temas e Modos (Dark e Light)

A customização do tema e do modo `dark` é feita de forma semelhante, porém a classe do tema deve ser definida pelo desenvolvedor, enquanto a classe do modo `dark` é injetada pelo `framework`, porém o recomendado é que ambas sejam inseridas no `body`.

Para ativar o modo `dark`, você deve alterar o `quasar.config.js > framework > config > dark`
**Quasar.App/quasar.config.js**
```
module.exports = function (ctx) {
  return {
    framework: {
      config: {
        dark: 'auto'
      }
    }
  }
}
```

abaixo segue um exemplo de customização para o modo `dark`.:
**Quasar.App/src/css/app.sass**
```sass
// app global css in Sass form
body.body--dark
  color: $grey-1
  --q-color-primary: #26a69a
  --q-color-secondary: #027BE3
  --q-color-accent: #9C27B0
  --q-color-positive: #21BA45
  --q-color-negative: #C10015
  --q-color-info: #31CCEC
  --q-color-warning: #F2C037
  .text-main
    color: $grey-10
  .text-content
    color: $grey-9
  .text-content-2
    color: $grey-8
  .bg-main
    background: $grey-10
  .bg-content
    background: $grey-9
  .bg-content-2
    background: $grey-8
body.body--light
  color: $grey-10
  --q-color-primary: #027BE3
  --q-color-secondary: #26A69A
  --q-color-accent: #9C27B0
  --q-color-positive: #21BA45
  --q-color-negative: #C10015
  --q-color-info: #31CCEC
  --q-color-warning: #F2C037
  .text-main
    color: white
  .text-content
    color: $grey-2
  .text-content-2
    color: $grey-4
  .bg-main
    background: white
  .bg-content
    background: $grey-2
  .bg-content-2
    background: $grey-4
```

No caso acima, criamos as cores personalizadas `main`, `content` e `content-2` para os modos `dark` e `light`, assim como definimos as cores base (`primary`, `secondary`, `accent`, `positive`, `negative`, `info`, `warning`)

Note que as cores definidas para o modo `dark`, não foram bem selecionas, por tanto pode haver um problema de contraste no modo `dark`

caso haja a necessidade de implementar múltiplos temas ou esquemas de cores, você poderá definir classes base com a seguinte estrutura: `body.${nome_tema}.body--dark` e `body.${nome_tema}.body--light`, e um nível abaixo, definir as customizações.

##19 Internacionalização - I18n

A internacionalização no Quasar é bem simples, basicamente são arquivos `JSON`, então cabe a você desenvolvedor definir como estes textos serão organizados.

Mas dado a estrutura deste projeto, não será possível a flexibilização quanto a estrutura dos textos usados nas validações, ou seja, os erros e os nomes dos rótulos dos campos, aqui chamados de `fields`.

Então, iremos criar dois arquivos dentro da pasta `Quasar.App/src/i18n`.
**Quasar.App/src/i18n/en-us/index.js**
```js
export default {
  validations: {
    compare: '{field} and {other} do not match',
    email: 'The {field} field is not a valid e-mail address',
    required: 'The {field} field is required'
  },
  fields: {
    confirmPassword: 'Confirm your Password',
    confirmUserName: 'Confirm your Email',
    firstName: 'First Name',
    lastName: 'Last Name',
    password: 'Password',
    userName: 'Email'
  }
}
```
**Quasar.App/src/i18n/pt-br/index.js**
```js
export default {
  validations: {
    compare: '{field} e {other} não são iguais',
    email: 'O campo {field} não possui um email válido',
    required: 'O campo {field} é requerido'
  },
  fields: {
    confirmPassword: 'Confirme à Senha',
    confirmUserName: 'Confirme o Email',
    firstName: 'Nome',
    lastName: 'Sobrenome',
    password: 'Senha',
    userName: 'Email'
  }
}
```

Apenas para ficar claro, os nomes entre `{}` são `placeholders`, por exemplo, `{field}` em `The {field} field is required` será substituído pelo nome do campo que está sendo validado.

Agora precisamos registrar estes textos localizados.

**Quasar.App/src/i18n/index.js**
```js
import enUS from './en-us'
import ptBR from './pt-br'

export default {
  'en-us': enUS,
  'pt-br': ptBR
}
```

##20 Validações

Iremos utilizar o sistema de validações que o Quasar oferece, porém iremos encapsular isto dentro de um serviço, para que posamos aplicar os textos localizados.

então, crie o seguinte serviço:

**Quasar.App/src/services/validations.js**
```js
const validations = {}
validations.required = ({ self, field }) => {
  return function required (val) {
    return !!val || self.$t('validations.required', {
      field: self.$t(`fields.${field}`)
    })
  }
}

const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
validations.email = ({ self, field }) => {
  return function required (val) {
    return emailRegex.test(val) || self.$t('validations.email', {
      field: self.$t(`fields.${field}`)
    })
  }
}

validations.server = ({ self, field }) => {
  return function server (val) {
    return self.validationArgs[field].server
  }
}

export default function validation (self, context) {
  const _validations = {}
  for (const field in context) {
    const _rules = []
    const rules = context[field]
    const names = Object.keys(validations)
    for (const rule of names) {
      if (rules.includes(rule)) {
        _rules.push(validations[rule]({ self, field }))
      }
    }
    _validations[field] = _rules
  }
  _validations.resetServer = function reset () {
    for (const field in context) {
      if (field in self.validationArgs && 'server' in self.validationArgs[field]) {
        self.validationArgs[field].server = true
      }
    }
  }
  return _validations
}
```

no serviço acima, as linas que merecem atenção são:

```js
function required (val) {
  return !!val || self.$t('validations.required', {
    field: self.$t(`fields.${field}`)
  })
}
```
```js
function email (val) {
  return emailRegex.test(val) || self.$t('validations.email', {
    field: self.$t(`fields.${field}`)
  })
}
```

Em ambos os casos, temos uma função que retorna um `boolean` ou uma `string`, caso retorne `true`, é por que passou no teste, se retornar uma `string`, é por que o teste falhou, e a `string` é a mensagem de erro. o `self.$t` serve para recuperamos esta mensagem já localizada.

```js
return function server (val) {
  return self.validationArgs[field].server
}
```

Já esta validação dependente de um dado proveniente do componente, neste caso, o retorno da API.

Desta forma, se quisemos validar o `userName` e o `password`, podemos montar as rules da seguinte forma.:

```js
import validations from 'services/validations'

export default {
  data () {
    const self = this
    const validation = validations(self, {
      userName: ['required', 'email', 'server'],
      password: ['required', 'server']
    })
    return {
      userName: '',
      password: '',
      validation,
      validationArgs: {
        userName: {
          server: true
        },
        password: {
          server: true
        }
      }
    }
  }
}
```
```html
<q-nput label="$t('fields.userName')" v-model="userName" :rules="validation.userName" @blur="validationArgs.userName.server = true" />
<q-nput label="$t('fields.password')" type="password" v-model="password" :rules="validation.password" @blur="validationArgs.password.server = true" />
```

Através do objeto `validationArgs`, conseguimos passar alguma informação complementar o serviço que constrói as validações, por exemplo, `validationArgs.userName.server` será utilizado pela validação `server`.

Lembrando que `userName` e `password` devem está presentes no arquivo de localização (i18n), assim como as regras `required`, `email` e `server`.

Assim como as regras utilizadas (no caso acima, `required`, `email` e `server`) devem está devidamente implementadas no serviço `validations`.

Nos próximos capítulos, nós veremos estas validações em ação.

E para fechamos estes assunto, precisamos criar um alias para a pasta `services`, o primeiro passo é adicionar o seguinte bloco de código no `quasar.config.js` > `build` > `extendWebpack`

**QPANC.App/quasar.config.js**
```js
const path = require('path')

module.exports = function (ctx) {
  return {
    build: {
      extendWebpack (cfg, { isServer, isClient }) {
        cfg.resolve.alias = {
          ...cfg.resolve.alias,
          services: path.resolve(__dirname, './src/services'),
        }
      }
    }
  }
}
```

e para permitir que o intellisense funcione no VSCode, precisamos adicionar o seguinte item `jsconfig.json` > `compilerOptions` > `paths`

**QPANC.App/jsconfig.json**
```json
{
  "compilerOptions": {
    "paths": {
      "services/*": [
        "src/services/*"
      ]
    }
  }
}
```

##20 Validações

Iremos utilizar o sistema de validações que o `Quasar` oferece, porém iremos encapsular isto dentro de um serviço, para que posamos aplicar os textos localizados.

então, crie o seguinte serviço:

**Quasar.App/src/services/validations.js**
```js
const validations = {}
validations.required = ({ self, field }) => {
  return function required (val) {
    return !!val || self.$t('validations.required', {
      field: self.$t(`fields.${field}`)
    })
  }
}

const emailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
validations.email = ({ self, field }) => {
  return function required (val) {
    return emailRegex.test(val) || self.$t('validations.email', {
      field: self.$t(`fields.${field}`)
    })
  }
}

validations.server = ({ self, field }) => {
  return function server (val) {
    return self.validationArgs[field].server
  }
}

export default function validation (self, context) {
  const _validations = {}
  for (const field in context) {
    const _rules = []
    const rules = context[field]
    const names = Object.keys(validations)
    for (const rule of names) {
      if (rules.includes(rule)) {
        _rules.push(validations[rule]({ self, field }))
      }
    }
    _validations[field] = _rules
  }
  _validations.resetServer = function reset () {
    for (const field in context) {
      if (field in self.validationArgs && 'server' in self.validationArgs[field]) {
        self.validationArgs[field].server = true
      }
    }
  }
  return _validations
}
```

no serviço acima, as linhas que merecem atenção são:

```js
function required (val) {
  return !!val || self.$t('validations.required', {
    field: self.$t(`fields.${field}`)
  })
}
```
```js
function email (val) {
  return emailRegex.test(val) || self.$t('validations.email', {
    field: self.$t(`fields.${field}`)
  })
}
```

Em ambos os casos, temos uma função que retorna um `boolean` ou uma `string`, caso retorne `true`, é por que passou no teste, se retornar uma `string`, é por que o teste falhou, e a `string` é a mensagem de erro. o `self.$t` serve para recuperamos esta mensagem já localizada.

```js
return function server (val) {
  return self.validationArgs[field].server
}
```

Já esta validação dependente de um dado proveniente do componente, neste caso, o retorno da API.

Desta forma, se quisemos validar o `userName` e o `password`, podemos montar as rules da seguinte forma.:

```js
import validations from 'services/validations'

export default {
  data () {
    const self = this
    const validation = validations(self, {
      userName: ['required', 'email', 'server'],
      password: ['required', 'server']
    })
    return {
      userName: '',
      password: '',
      validation,
      validationArgs: {
        userName: {
          server: true
        },
        password: {
          server: true
        }
      }
    }
  }
}
```
```html
<q-nput label="$t('fields.userName')" v-model="userName" :rules="validation.userName" @blur="validationArgs.userName.server = true" />
<q-nput label="$t('fields.password')" type="password" v-model="password" :rules="validation.password" @blur="validationArgs.password.server = true" />
```

Através do objeto `validationArgs`, conseguimos passar alguma informação complementar ao serviço que constrói as validações, por exemplo, `validationArgs.userName.server` será utilizado pela validação `server`.

Lembrando que `userName` e `password` devem está presentes no arquivo de localização (`i18n`), assim como as regras `required`, `email` e `server`.

Assim como as regras utilizadas (no caso acima, `required`, `email` e `server`) devem está devidamente implementadas no serviço `validations`.

Nos próximos capítulos, nós veremos mais sobre estas validações.

E para fechamos estes assunto, precisamos criar um alias para a pasta `services`, o primeiro passo é adicionar o seguinte bloco de código no `quasar.config.js > build > extendWebpack`

**QPANC.App/quasar.config.js**
```js
const path = require('path')

module.exports = function (ctx) {
  return {
    build: {
      extendWebpack (cfg, { isServer, isClient }) {
        cfg.resolve.alias = {
          ...cfg.resolve.alias,
          services: path.resolve(__dirname, './src/services'),
        }
      }
    }
  }
}
```

e para permitir que o intellisense funcione no VSCode, precisamos adicionar o seguinte item `jsconfig.json` > `compilerOptions` > `paths`

**QPANC.App/jsconfig.json**
```json
{
  "compilerOptions": {
    "paths": {
      "services/*": [
        "src/services/*"
      ]
    }
  }
}
```

##21 Instalando extensões

Como a aplicação está rodando em uma `VM`, e ao instalar uma extensão será adicionado um pacote pelo `npm` ou `yarn`. você terá que adotar uma das seguintes rotinas.:

###21.1 - quasar ext add extensao_id

Você poderá adicionar a extensão usando o `quasar ext add extensao_id`, porém será preciso excluir a pasta `node_modules` e os arquivos de lock (`yarn.lock` e/ou `package-lock.json`). exemplo:

```
quasar ext invoke qautomate
```

###21.2 - quasar ext invoke extensao_id

Para usar o invoke, é preciso instalar o pacote utilizado pela extensão antes, por exemplo, para a extensão `qautomate`:

**QPANC.App/package.json**
```json
{
  "devDependencies": {
    "quasar-app-extension-qautomate": "^1.0.0-alpha.5"
  }
}
```

então execute a aplicação para que o pacote seja instalado a partir da `VM` gerenciada pelo `Docker`.

Após parar o container, podemos usar o invoke:

```
quasar ext invoke qautomate
```

###21.3 - Considerações

Eu sempre opto por fazer o `add`, então apago o `node_modules` e o `yarn.lock`, por mais que seja mais demorado, é mais pratico, menos propenso a erros e me dá uma desculpa para ir tomar um café.