Skip to content

Commit

Permalink
Merge pull request #4169 from systeminit/jobelenus/install-modules
Browse files Browse the repository at this point in the history
Show modules to install, and let people install them!
  • Loading branch information
jobelenus authored Jul 17, 2024
2 parents 752576c + e9ee675 commit 7f712e1
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 24 deletions.
23 changes: 17 additions & 6 deletions app/web/src/components/CustomizeTabs.vue
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
<template>
<TabGroup
ref="group"
:startSelectedTabSlug="tabContentSlug"
marginTop="2xs"
variant="fullsize"
@update:selected-tab="onTabChange"
>
<TabGroupItem slug="assets" label="ASSETS">
<slot v-if="tabContentSlug === 'assets'" />
<slot name="assets" />
</TabGroupItem>
<TabGroupItem slug="newassets">
<template #label>
NEW ASSETS <PillCounter :count="assetStore.installableModules.length" />
</template>
<slot name="newassets" />
</TabGroupItem>
<TabGroupItem
v-if="featureFlagsStore.MODULES_TAB"
slug="packages"
label="MODULES"
>
<slot v-if="tabContentSlug === 'packages'" />
<slot name="packages" />
</TabGroupItem>
</TabGroup>
</template>

<script lang="ts" setup>
import { useRouter, useRoute, RouteLocationNamedRaw } from "vue-router";
import { PropType } from "vue";
import { TabGroup, TabGroupItem } from "@si/vue-lib/design-system";
import { PropType, ref } from "vue";
import { TabGroup, TabGroupItem, PillCounter } from "@si/vue-lib/design-system";
import { useFeatureFlagsStore } from "@/store/feature_flags.store";
import { useAssetStore } from "@/store/asset.store";
Expand All @@ -30,9 +37,11 @@ const route = useRoute();
const featureFlagsStore = useFeatureFlagsStore();
const assetStore = useAssetStore();
const group = ref<InstanceType<typeof TabGroup>>();
defineProps({
tabContentSlug: {
type: String as PropType<"assets" | "functions" | "packages">,
type: String as PropType<"assets" | "newassets" | "packages">,
required: true,
},
});
Expand All @@ -44,9 +53,11 @@ function onTabChange(tabSlug?: string) {
const params = {
name: `workspace-lab-${tabSlug}`,
} as RouteLocationNamedRaw;
if (tabSlug === "assets")
if (tabSlug !== "packages")
params.query = assetStore.syncSelectionIntoUrl(true);
router.push(params);
}
}
defineExpose({ group });
</script>
163 changes: 163 additions & 0 deletions app/web/src/components/InstallAsset.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<template>
<ScrollArea
class="flex flex-col h-full border border-t-0 border-neutral-300 dark:border-neutral-600"
>
<template #top>
<div class="p-xs">
<TruncateWithTooltip
class="text-2xl font-bold pb-2xs flex flex-row items-center gap-xs"
>
<Icon name="component" size="xl" />
<div class="flex flex-row items-center gap-xs">
{{ $props.moduleName }}
</div>
</TruncateWithTooltip>
<VButton
icon="component-plus"
label="Install Module"
:loading="installReqStatus.isPending"
loadingText="Installing..."
:requestStatus="installReqStatus"
successText="Successfully Installed"
tone="action"
@click="install"
/>
</div>
<div
v-if="moduleObj"
class="text-xs italic flex flex-row flex-wrap gap-x-lg text-neutral-600 dark:text-neutral-200"
>
<div>
<span class="font-bold">Name: </span>
{{ moduleObj.name }}
</div>
<div>
<span class="font-bold">Module Created At: </span>
<Timestamp :date="moduleObj.createdAt" size="long" />
</div>
<div>
<span class="font-bold">Created By: </span>{{ moduleObj.createdBy }}
</div>
<div>
<span class="font-bold">Version: </span>{{ moduleObj.version }}
</div>
</div>
</template>

<div
v-if="loadModuleReqStatus.isError || !loadModuleReqStatus.isRequested"
class="p-2 text-center text-neutral-400 dark:text-neutral-300"
>
<template v-if="moduleId">
Cannot retrieve details for "{{ moduleId }}"
</template>
</div>
<template v-else-if="moduleObj">
<CodeEditor
:id="`module-asset-${moduleId}`"
v-model="assetFn.code"
disabled
:recordId="moduleId"
/>
<template v-for="func in functions" :key="func.id">
<h2 class="text-xl">{{ func.name }}</h2>
<CodeEditor
:id="`module-${func.name}-${moduleId}`"
v-model="func.code"
disabled
:recordId="moduleId"
/>
</template>
</template>
<RequestStatusMessage
v-else
:requestStatus="loadModuleReqStatus"
loadingMessage="Retrieving Module"
/>
</ScrollArea>
</template>

<script lang="ts" setup>
import { watch, computed } from "vue";
import * as _ from "lodash-es";
import {
RequestStatusMessage,
ScrollArea,
Timestamp,
VButton,
Icon,
} from "@si/vue-lib/design-system";
import { useModuleStore, ModuleSpec } from "@/store/module.store";
import { ModuleId } from "@/api/sdf/dal/schema";
import { nilId } from "@/utils/nilId";
import TruncateWithTooltip from "./TruncateWithTooltip.vue";
import CodeEditor from "./CodeEditor.vue";
const props = defineProps<{
moduleId: ModuleId;
moduleName: string;
}>();
const moduleStore = useModuleStore();
const loadModuleReqStatus = moduleStore.getRequestStatus(
"GET_REMOTE_MODULE_SPEC",
props.moduleId,
);
const installReqStatus = moduleStore.getRequestStatus(
"INSTALL_REMOTE_MODULE",
props.moduleId,
);
const moduleObj = computed<ModuleSpec | undefined>(
() => moduleStore.remoteModuleSpecsById[props.moduleId],
);
interface FuncDisplay {
id: string;
name: string;
code: string;
}
const defaultAssetFn = {
id: nilId(),
name: "",
code: "",
};
const assetFn = computed<FuncDisplay>(() => {
if (!moduleObj.value) return defaultAssetFn;
const f = moduleObj.value.funcs
.filter((f) => !f.name.startsWith("si:"))
.find((f) => f.data.backendKind === "jsSchemaVariantDefinition");
if (!f) {
return defaultAssetFn;
} else {
return {
id: f.uniqueId,
name: f.name,
code: Buffer.from(f.data.codeBase64 || "", "base64").toString(),
};
}
});
const functions = computed<FuncDisplay[]>(() => {
if (!moduleObj.value) return [];
return moduleObj.value.funcs
.filter((f) => !f.name.startsWith("si:"))
.filter((f) => f.data.backendKind !== "jsSchemaVariantDefinition")
.map((f) => {
const code = Buffer.from(f.data.codeBase64 || "", "base64").toString();
const display = {} as FuncDisplay;
display.id = f.uniqueId;
display.name = f.name;
display.code = code;
return display;
});
});
const install = () => moduleStore.INSTALL_REMOTE_MODULE(props.moduleId);
watch(
() => props.moduleId,
() => {
moduleStore.GET_REMOTE_MODULE_SPEC(props.moduleId);
},
{ immediate: true },
);
</script>
104 changes: 104 additions & 0 deletions app/web/src/components/InstallAssetsPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<ScrollArea>
<RequestStatusMessage
v-if="loadModulesReqStatus.isPending"
:requestStatus="loadModulesReqStatus"
loadingMessage="Loading modules..."
/>
<template #top>
<SidebarSubpanelTitle icon="component">
<template #label>
<div class="flex flex-row gap-xs">
<div>Install New Assets</div>
</div>
</template>
</SidebarSubpanelTitle>
<SiSearch
ref="searchRef"
placeholder="search assets"
@search="onSearch"
/>
</template>
<ul
v-if="assetStore.installableModules.length > 0"
:class="
clsx(
'dark:text-white text-black dark:bg-neutral-800 py-[1px]',
'hover:dark:outline-action-300 hover:outline-action-500 hover:outline hover:z-10 hover:-outline-offset-1 hover:outline-1',
)
"
>
<li
v-for="module in filteredModules"
:key="module.id"
:class="
clsx(
'text-xs w-full p-2xs truncate flex flex-row items-center gap-1 hover:text-action-500 dark:hover:text-action-300 cursor-pointer',
selectedModule &&
module.id === selectedModule.id &&
themeClasses('bg-action-100', 'bg-action-900'),
)
"
@click="() => selectModule(module)"
>
<div class="truncate">
{{ module.name }}
</div>
</li>
</ul>
<EmptyStateCard
v-else
iconName="no-assets"
primaryText="No Installable Assets"
secondaryText="Check back later when more assets are contributed."
/>
</ScrollArea>
</template>

<script lang="ts" setup>
import * as _ from "lodash-es";
import clsx from "clsx";
import { ref, computed } from "vue";
import {
ScrollArea,
RequestStatusMessage,
themeClasses,
} from "@si/vue-lib/design-system";
import SiSearch from "@/components/SiSearch.vue";
import { useAssetStore } from "@/store/asset.store";
import { Module } from "@/api/sdf/dal/schema";
import router from "@/router";
import EmptyStateCard from "./EmptyStateCard.vue";
import SidebarSubpanelTitle from "./SidebarSubpanelTitle.vue";
const assetStore = useAssetStore();
const searchRef = ref<InstanceType<typeof SiSearch>>();
const searchString = ref("");
const onSearch = (search: string) => {
searchString.value = search.trim().toLocaleLowerCase();
};
const filteredModules = computed(() =>
assetStore.installableModules.filter((m) =>
m.name.toLocaleLowerCase().includes(searchString.value),
),
);
const loadModulesReqStatus = assetStore.getRequestStatus("LOAD_MODULES");
const selectedModule = ref<Module | undefined>();
const selectModule = (module: Module) => {
selectedModule.value = module;
const newQueryObj = {
...{ m: module.id },
};
router.replace({
query: newQueryObj,
});
};
defineExpose({ selectedModule });
</script>
21 changes: 18 additions & 3 deletions app/web/src/components/Workspace/WorkspaceCustomizeAssets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
<template #subpanel1>
<div class="flex flex-col h-full">
<div class="relative flex-grow">
<CustomizeTabs tabContentSlug="assets">
<AssetListPanel />
<CustomizeTabs ref="tabs" tabContentSlug="assets">
<template #assets>
<AssetListPanel />
</template>
<template #newassets>
<InstallAssetsPanel ref="install" />
</template>
</CustomizeTabs>
</div>
</div>
</template>
<template #subpanel2>
<template v-if="tabs?.group?.selectedTabSlug !== 'newassets'" #subpanel2>
<AssetFuncListPanel :schemaVariantId="selectedVariantId" />
</template>
</component>
Expand All @@ -33,6 +38,11 @@
v-else-if="selectedVariantId"
:schemaVariantId="selectedVariantId"
/>
<InstallAsset
v-else-if="!!install?.selectedModule"
:moduleId="install?.selectedModule.id"
:moduleName="install?.selectedModule.name"
/>
<WorkspaceCustomizeEmptyState
v-else
:instructions="
Expand Down Expand Up @@ -125,6 +135,8 @@ import { useAssetStore } from "@/store/asset.store";
import { useFuncStore } from "@/store/func/funcs.store";
import AssetCard from "../AssetCard.vue";
import AssetListPanel from "../AssetListPanel.vue";
import InstallAssetsPanel from "../InstallAssetsPanel.vue";
import InstallAsset from "../InstallAsset.vue";
import CustomizeTabs from "../CustomizeTabs.vue";
import AssetDetailsPanel from "../AssetDetailsPanel.vue";
import AssetFuncListPanel from "../AssetFuncListPanel.vue";
Expand All @@ -144,10 +156,13 @@ const selectedFuncId = computed(() => funcStore.selectedFuncId);
const loadAssetsRequestStatus = assetStore.getRequestStatus(
"LOAD_SCHEMA_VARIANT_LIST",
);
const install = ref<InstanceType<typeof InstallAssetsPanel>>();

const leftResizablePanelRef = ref<InstanceType<typeof ResizablePanel>>();
const rightResizablePanelRef = ref<InstanceType<typeof ResizablePanel>>();

const tabs = ref<InstanceType<typeof CustomizeTabs>>();

const contextMenuRef = ref<InstanceType<typeof DropdownMenu>>();

const open = (mouse: MouseEvent) => {
Expand Down
Loading

0 comments on commit 7f712e1

Please sign in to comment.