feat(dui3): adds theme config switching
This commit is contained in:
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
Reference in New Issue
Block a user