Skip to main content
Glama
App.vue9.15 kB
<!-- eslint-disable vue/no-multiple-template-root --> <template> <Suspense> <template #default> <div class="font-sans"> <template v-if="route.name === 'auth-connect'"> <RouterView /> </template> <template v-else-if=" !authStore.userIsLoggedInAndInitialized && (!restoreAuthReqStatus.isRequested || restoreAuthReqStatus.isPending || reconnectAuthReqStatus.isPending) " > <p>restoring auth...</p> </template> <template v-else> <CachedAppNotification /> <RouterView :key="selectedWorkspace?.pk" /> </template> </div> </template> <template #fallback>Loading...</template> </Suspense> <div v-if="suspenseError">Suspense Error {{ suspenseError }}</div> </template> <script lang="ts" setup> import { computed, onBeforeUnmount, onMounted, ref, onErrorCaptured, provide, watch, } from "vue"; import "floating-vue/dist/style.css"; import { tw } from "@si/vue-lib"; import { useThemeContainer } from "@si/vue-lib/design-system"; import SiLogoUrlLight from "@si/vue-lib/brand-assets/si-logo-symbol-white-bg.svg?url"; import SiLogoUrlDark from "@si/vue-lib/brand-assets/si-logo-symbol-black-bg.svg?url"; import { useHead } from "@vueuse/head"; import { useRoute } from "vue-router"; import { useCustomFontsLoadedProvider } from "./utils/useFontLoaded"; import { useAuthStore } from "./store/auth.store"; import { useWorkspacesStore } from "./store/workspaces.store"; import { useRealtimeStore } from "./store/realtime/realtime.store"; import CachedAppNotification from "./components/CachedAppNotification.vue"; import { APP_MINIMUM_WIDTH } from "./main"; const suspenseError = ref(); onErrorCaptured((err) => { suspenseError.value = err; }); // this TS magic means that when you call Object.entries // the "key" will retain its type and will not just be defaulted to "string" type Entries<T> = { [K in keyof T]: [K, T[K]]; }[keyof T][]; declare global { interface ObjectConstructor { entries<T extends object>(o: T): Entries<T>; } } useCustomFontsLoadedProvider(); // provides the root theme value to all children, and returns that root theme to use below const { theme: rootTheme } = useThemeContainer(); // watch the window size to enforce minimum window width const windowWidth = ref(window.innerWidth); const windowSizeClasses = computed(() => windowWidth.value < APP_MINIMUM_WIDTH ? tw`min-w-[700px] overflow-x-auto` // APP_MINIMUM_WIDTH : "", ); const windowResizeHandler = () => { windowWidth.value = window.innerWidth; }; onMounted(() => { windowResizeHandler(); window.addEventListener("resize", windowResizeHandler); }); onBeforeUnmount(() => { window.removeEventListener("resize", windowResizeHandler); }); const loadingGuard = ref(false); provide("LOADINGGUARD", { loadingGuard, }); useHead( computed(() => ({ bodyAttrs: { // add some base classes we need these type classes set for capsize plugin to work throughout // and add dark mode style/class class: tw`font-sans text-base leading-none fixed ${rootTheme.value} ${windowSizeClasses.value}`, }, htmlAttrs: { style: `color-scheme: ${rootTheme.value};`, }, link: [ { rel: "icon", href: rootTheme.value === "light" ? SiLogoUrlLight : SiLogoUrlDark, }, ], // set up title template and a default titleTemplate: "SI | %s", title: "Workspace", })), ); // check for auth token in local storage and initialize auth in store if found // this token will be automatically injected into API requests const authStore = useAuthStore(); const route = useRoute(); if (route.name === "auth-connect") { // we are just clearing any local login state since we are in the process of logging in again authStore.localLogout(false); } else { authStore.initFromStorage(); } const restoreAuthReqStatus = authStore.getRequestStatus("RESTORE_AUTH"); const reconnectAuthReqStatus = authStore.getRequestStatus("AUTH_RECONNECT"); const workspacesStore = useWorkspacesStore(); const selectedWorkspace = computed(() => workspacesStore.selectedWorkspace); // initialize the realtime store - which will watch for auth and open/close websocket const realtimeStore = useRealtimeStore(); const user = computed(() => authStore.user); const subscribed = ref(false); watch([selectedWorkspace, user], () => { const workspaceId = selectedWorkspace.value?.id; const userPk = user.value?.pk; if (subscribed.value || workspaceId === undefined || userPk === undefined) return; subscribed.value = true; // the authStore does not run the activated hook so we let the App handle its subscriptions. // This makes sure we always subscribe on a workspace id, but only once, since the id may not be defined at the start but will never change. realtimeStore.subscribe("auth", `workspace/${workspaceId}`, [ { eventType: "UserWorkspaceFlagsUpdated", callback: (payload) => { if (userPk !== payload.userPk) return; authStore.updateFlags(payload.flags); }, }, ]); }); </script> <style lang="less"> .v-popper__arrow-container { display: none; } .v-popper__inner { border-radius: 0px !important; border-color: #5a5a5a !important; max-width: 80vw; overflow-wrap: break-word; } .v-popper--theme-w-380 > .v-popper__wrapper > .v-popper__inner { max-width: 380px; } .v-popper--theme-attribute-docs > .v-popper__wrapper > .v-popper__inner { border-radius: 0.5rem !important; max-width: 420px; } .v-popper--theme-user-info, .v-popper--theme-html { margin-top: 12px; font-style: italic; border-radius: 1rem; } .v-popper--theme-user-info { font-size: 1.25rem; } .v-popper--theme-user-info > .v-popper__wrapper > .v-popper__inner { border-radius: 0.5rem !important; padding-left: 0; padding-right: 0; } .v-popper--theme-html > .v-popper__wrapper > .v-popper__inner, .v-popper--theme-attribute-source-icon > .v-popper__wrapper > .v-popper__inner { border-radius: 0.5rem !important; } .bg-caution-lines-light { background: repeating-linear-gradient( -45deg, #fff, #fff 10px, #ccc 10px, #ccc 20px ); } .bg-caution-lines-dark { background: repeating-linear-gradient( -45deg, #000, #000 10px, #333 10px, #333 20px ); } @keyframes siToastFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes siToastFadeOut { from { opacity: 1; } to { opacity: 0; } } .si-toast-fade-enter-active { animation-name: siToastFadeIn; animation-duration: 250ms; animation-fill-mode: both; } .si-toast-fade-leave-active { animation-name: siToastFadeOut; animation-duration: 250ms; animation-fill-mode: both; } .si-toast-fade-move { transition-timing-function: ease-in-out; transition-property: all; transition-duration: 400ms; } .si-toast-no-defaults { padding: 0; margin: 0; min-width: 0; min-height: 0; } body.light { --toast-text-color: @colors-black; --toast-bg-color: @colors-white; .v-popper--theme-attribute-docs > .v-popper__wrapper > .v-popper__inner { color: #000; background-color: #fff; border: 1px solid #000; } .v-popper--theme-html > .v-popper__wrapper > .v-popper__inner, .v-popper--theme-attribute-source-icon > .v-popper__wrapper > .v-popper__inner { color: #000; background-color: #fff; border: 1px solid #000; } } body.dark { --toast-text-color: @colors-white; --toast-bg-color: @colors-black; .v-popper--theme-attribute-docs > .v-popper__wrapper > .v-popper__inner { color: #fff; background-color: #000; border: 1px solid #fff; border-color: #fff !important; } .v-popper--theme-html > .v-popper__wrapper > .v-popper__inner, .v-popper--theme-attribute-source-icon > .v-popper__wrapper > .v-popper__inner { color: #fff; background-color: rgb(0, 0, 0); border: 1px solid #fff; border-color: #fff !important; } } .Vue-Toastification__container { & .Vue-Toastification__toast { background-color: var(--toast-bg-color); color: var(--toast-text-color); .Vue-Toastification__progress-bar { background-color: var(--toast-text-color); } } } .Vue-Toastification__container.diagram-toast-container.top-left, .Vue-Toastification__container.diagram-toast-container.top-right, .Vue-Toastification__container.diagram-toast-container.top-center { position: absolute; // default is fixed, but we want it positioned within its container, so go absolute padding: 0; top: 70px; // this puts the toasts 10px below the NavBar at the top of the screen } .Vue-Toastification__container.diagram-toast-container.bottom-left, .Vue-Toastification__container.diagram-toast-container.bottom-right, .Vue-Toastification__container.diagram-toast-container.bottom-center { position: absolute; // default is fixed, but we want it positioned within its container, so go absolute padding: 0; bottom: 58px; // this puts the toasts 10px above the bottom bar of the Explore page } </style>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server