feat(dui3): adds theme config switching

This commit is contained in:
Dimitrie Stefanescu
2023-07-23 20:30:40 +01:00
parent 3b4b8c3f0d
commit c6c1c1c024
8 changed files with 134 additions and 87 deletions
+8 -4
View File
@@ -6,18 +6,22 @@
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useAccountsSetup } from '~/lib/accounts/composables/setup'
// import { useDocumentInfoSetup } from '~/lib/document-info'
import { useDocumentInfoStore } from '~/store/uiConfig'
const uiConfigStore = useDocumentInfoStore()
const { isDarkTheme } = storeToRefs(uiConfigStore)
useAccountsSetup()
// useDocumentInfoSetup()
useHead({
// Title suffix
titleTemplate: (titleChunk) =>
titleChunk ? `${titleChunk} - Speckle DUIv3` : 'Speckle DUIv3',
titleChunk ? `${titleChunk as string} - Speckle DUIv3` : 'Speckle DUIv3',
htmlAttrs: {
lang: 'en'
lang: 'en',
class: computed(() => (isDarkTheme.value ? `dark` : ``))
},
bodyAttrs: {
class: 'simple-scrollbar bg-foundation-page text-foreground'
@@ -1,7 +1,7 @@
<template>
<button
v-tippy="tip"
:class="`block min-w-0 w-full max-w-full text-left items-center justify-between space-x-2 hover:bg-primary-muted transition p-2 select-none group hover:cursor-pointer hover:text-primary ${
:class="`block w-full text-left items-center space-x-2 hover:bg-primary-muted transition p-2 select-none group hover:cursor-pointer hover:text-primary ${
!account.isValid ? 'text-danger bg-rose-500/10' : ''
} ${account.accountInfo.isDefault ? 'bg-blue-500/5' : ''}`"
@click="$openUrl(account.accountInfo.serverInfo.url)"
+33 -16
View File
@@ -21,7 +21,7 @@
class="absolute right-0 md:right-4 top-14 md:top-16 w-full md:w-64 origin-top-right bg-foundation sm:rounded-t-md rounded-b-md shadow-lg overflow-hidden"
>
<MenuItem>
<div class="border border-t-1">
<div class="border border-t-1 border-primary-muted">
<div v-if="loading" class="p-2">Loading accounts...</div>
<div v-else class="p-2 flex items-center justify-between">
<div class="text-xs text-foreground-2">Your accounts</div>
@@ -45,20 +45,32 @@
</div>
</MenuItem>
<MenuItem v-slot="{ close }" as="div">
<div class="p-2 flex space-x-2 border-t-1">
<button
class="text-xs text-foreground-2 hover:text-primary transition"
@click="$showDevTools"
>
Dev tools
</button>
<NuxtLink
class="text-xs text-foreground-2 hover:text-primary transition"
to="/test"
@click="close()"
>
Test Page
</NuxtLink>
<div class="py-3 flex space-x-2 border-t-1 justify-around">
<div class="">
<button
class="text-xs text-foreground-2 hover:text-primary transition"
@click="$showDevTools"
>
Open Dev Tools
</button>
<NuxtLink
class="text-xs text-foreground-2 hover:text-primary transition"
to="/test"
@click="close()"
>
Test Page
</NuxtLink>
</div>
<!--
NOTE: Here's an example of customising the frontend app based on what bindings we
have loaded. E.g., if config bindings are not present, we do not show any button
regarding switching themes.
-->
<div v-if="hasConfigBindings">
<FormButton size="xs" text @click.stop="toggleTheme()">
{{ isDarkTheme ? 'Switch To Light Theme' : 'Switch To Dark Theme' }}
</FormButton>
</div>
</div>
</MenuItem>
</MenuItems>
@@ -71,11 +83,16 @@ import { XMarkIcon } from '@heroicons/vue/20/solid'
import { storeToRefs } from 'pinia'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
import { useAccountStore } from '~/store/accounts'
import { DUIAccount } from 'lib/accounts/composables/setup'
import { DUIAccount } from '~/lib/accounts/composables/setup'
import { useDocumentInfoStore } from '~/store/uiConfig'
const accountStore = useAccountStore()
const { accounts, defaultAccount, loading } = storeToRefs(accountStore)
const uiConfigStore = useDocumentInfoStore()
const { isDarkTheme, hasConfigBindings } = storeToRefs(uiConfigStore)
const { toggleTheme } = uiConfigStore
const { $showDevTools } = useNuxtApp()
const user = computed(() => {
@@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/require-await */
import { BaseBridge } from '~~/lib/bridge/base'
import { IBinding } from '~~/lib/bindings/definitions/IBinding'
/**
* The name under which this binding will be registered.
*/
export const IConfigBindingKey = 'configBinding'
/**
* A test binding interface to ensure compatbility. Ideally all host environments would implement and register it.
*/
export interface IConfigBinding extends IBinding<IConfigBindingEvents> {
getConfig: () => Promise<Config>
updateConfig: (config: Config) => Promise<void>
}
export interface IConfigBindingEvents {
void: () => void
}
export type Config = {
darkTheme: boolean
}
export class MockedConfigBinding extends BaseBridge {
constructor() {
super()
}
getConfig() {
return {
darkTheme: false
}
}
updateConfig(config: Config) {
// do nothing
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ export class GenericBridge extends BaseBridge {
try {
availableMethodNames = await this.bridge.GetBindingsMethodNames()
} catch (e) {
console.error(e)
console.warn(`Failed to get method names.`)
return false
}
+6 -65
View File
@@ -1,69 +1,10 @@
<template>
<div>
<div class="space-y-2">
<div v-if="!loading">
Hello world! You have
{{ accounts.length }} accounts, out of which {{ validAccounts.length }} are
valid.
</div>
<div v-else>Loading Accounts...</div>
<div>
<FormButton to="/test">Go To Test Bindings Page</FormButton>
</div>
<div v-for="acc in accounts" :key="acc.accountInfo.id">
<div class="truncate text-xs">
{{ acc.isValid }} // {{ acc.accountInfo.userInfo.email }} @
<b>{{ acc.accountInfo.serverInfo.url }}</b>
{{ acc.accountInfo.serverInfo.name }}
</div>
</div>
<div>
Your default account is at {{ defaultAccount?.accountInfo.serverInfo.url }}
</div>
<div>
<div v-for="(res, clientId) in queries" :key="clientId">
<strong>{{ clientId }}:</strong>
{{ res.result.value?.serverInfo.version || res.error }}
</div>
</div>
<div>Doc info:</div>
<div>{{ documentInfo }}</div>
<div>
<FormButton @click="accountStore.refreshAccounts()">
Refresh Accounts
</FormButton>
</div>
<div class="flex items-center justify-center h-[calc(100vh-14rem)]">
<div
class="p-2 bg-primary text-foreground-on-primary shadow-md rounded-md font-bold"
>
TODO: Everything
</div>
</div>
</template>
<script setup lang="ts">
import { UseQueryReturn, useQuery } from '@vue/apollo-composable'
import { storeToRefs } from 'pinia'
import { graphql } from '~/lib/common/generated/gql'
import { ServerInfoTestQuery } from '~/lib/common/generated/gql/graphql'
import { useAccountStore } from '~/store/accounts'
import { useDocumentInfoStore } from '~/store/documentInfo'
const accountStore = useAccountStore()
const { accounts, defaultAccount, loading, validAccounts } = storeToRefs(accountStore)
const { documentInfo } = storeToRefs(useDocumentInfoStore())
const versionQuery = graphql(`
query ServerInfoTest {
serverInfo {
version
}
}
`)
const clientIds = validAccounts.value.map((a) => a.accountInfo.id)
const queries: Record<
string,
UseQueryReturn<ServerInfoTestQuery, Record<string, never>>
> = {}
for (const clientId of clientIds) {
queries[clientId] = useQuery(versionQuery, undefined, { clientId })
}
</script>
<script setup lang="ts"></script>
+10
View File
@@ -15,6 +15,12 @@ import {
MockedTestBinding
} from '~/lib/bindings/definitions/ITestBinding'
import {
IConfigBinding,
IConfigBindingKey,
MockedConfigBinding
} from '~/lib/bindings/definitions/IConfigBinding'
// Makes TS happy
declare let globalThis: Record<string, unknown> & {
CefSharp?: { BindObjectAsync: (name: string) => Promise<void> }
@@ -40,6 +46,9 @@ export default defineNuxtPlugin(async () => {
(await tryHoistBinding<IBasicConnectorBinding>(IBasicConnectorBindingKey)) ||
new MockedBaseBinding()
// UI configuration bindings.
const configBinding = await tryHoistBinding<IConfigBinding>(IConfigBindingKey)
const showDevTools = () => {
baseBinding.showDevTools()
}
@@ -53,6 +62,7 @@ export default defineNuxtPlugin(async () => {
testBindings,
nonExistantBindings,
baseBinding,
configBinding,
showDevTools,
openUrl
}
+34
View File
@@ -0,0 +1,34 @@
import { defineStore } from 'pinia'
import { Config } from 'lib/bindings/definitions/IConfigBinding'
export const useDocumentInfoStore = defineStore('documentInfoStore', () => {
const { $configBinding } = useNuxtApp()
const hasConfigBindings = ref(!!$configBinding)
const uiConfig = ref<Config>({ darkTheme: false })
watch(
uiConfig,
async (newValue) => {
if (!newValue || !$configBinding) return
await $configBinding.updateConfig(newValue)
},
{ deep: true }
)
const isDarkTheme = computed(() => {
return uiConfig.value?.darkTheme
})
const toggleTheme = () => {
uiConfig.value.darkTheme = !uiConfig.value.darkTheme
}
const init = async () => {
if (!$configBinding) return
uiConfig.value = await $configBinding.getConfig()
}
void init()
return { hasConfigBindings, isDarkTheme, toggleTheme }
})