Skip to content

Commit

Permalink
Doc panel improvements (#8530)
Browse files Browse the repository at this point in the history
Fixes parts of #8135

- Fixed rendering of the documentation panel when no entry is selected.
- Multiple improvements of tags in the documentation panel:
- Group color for `GROUP` tag
- Custom styling for `DEPRECATED`, `UNSTABLE`, and `ADVANCED` tags.
- Sorting
- Collapsing tags that exceed one line with a button
- Filtering out some tags (`ICON` and `TEXT_ONLY` for now)
- Filtering out private items from the documentation
- Fixing group colors (was incorrectly assuming unique group names) and fallback color (was incorrectly replacing every character in the name with `-` before hashing, which was hilarious).
- Fixing ComponentBrowser demo scene.
- Minor fixes for mock database in demo scenes (removed duplicated entry, added an additional one for testing tags)


https://github.com/enso-org/enso/assets/6566674/10f4f5d0-9332-40c6-93c6-8263eb5fd85a
  • Loading branch information
vitvakatu authored Dec 15, 2023
1 parent c109886 commit 41cb4e3
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 94 deletions.
13 changes: 9 additions & 4 deletions app/gui2/src/components/DocumentationPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const types = computed<TypeDocs[]>(() => {
return docs.kind === 'Module' ? docs.types : []
})
const isPlaceholder = computed(() => 'Placeholder' in documentation.value)
const isPlaceholder = computed(() => documentation.value.kind === 'Placeholder')
const name = computed<Opt<QualifiedName>>(() => {
const docs = documentation.value
Expand Down Expand Up @@ -116,7 +116,6 @@ function handleBreadcrumbClick(index: number) {

<template>
<div class="DocumentationPanel scrollable" @wheel.stop.passive>
<h1 v-if="documentation.kind === 'Placeholder'">{{ documentation.text }}</h1>
<Breadcrumbs
v-if="!isPlaceholder"
:breadcrumbs="breadcrumbs"
Expand All @@ -128,9 +127,15 @@ function handleBreadcrumbClick(index: number) {
@forward="historyStack.forward()"
@backward="historyStack.backward()"
/>
<DocsTags v-if="sections.tags.length > 0" class="tags" :tags="sections.tags" />
<DocsTags
v-if="sections.tags.length > 0"
class="tags"
:tags="sections.tags"
:groupColor="color"
/>
<div class="sections">
<span v-if="sections.synopsis.length == 0">{{ 'No documentation available.' }}</span>
<h2 v-if="documentation.kind === 'Placeholder'">{{ documentation.text }}</h2>
<span v-if="sections.synopsis.length == 0">No documentation available.</span>
<DocsSynopsis :sections="sections.synopsis" />
<DocsHeader v-if="types.length > 0" kind="types" label="Types" />
<DocsList
Expand Down
31 changes: 31 additions & 0 deletions app/gui2/src/components/DocumentationPanel/DocsTag.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { View } from './DocsTags.vue'
const props = defineProps<{ view: View; parentOffset: number; forceShow: boolean }>()
const emit = defineEmits<{ hidden: [] }>()
const tagRef = ref<HTMLDivElement>()
const shouldBeHidden = computed(() => {
if (tagRef.value == null) return false
return tagRef.value.offsetTop > props.parentOffset
})
watch(shouldBeHidden, (m) => m && emit('hidden'))
</script>

<template>
<div
ref="tagRef"
:class="{ hide: shouldBeHidden && !props.forceShow }"
:style="props.view.style || {}"
>
{{ props.view.label }}
</div>
</template>

<style scoped>
.hide {
display: none;
}
</style>
106 changes: 87 additions & 19 deletions app/gui2/src/components/DocumentationPanel/DocsTags.vue
Original file line number Diff line number Diff line change
@@ -1,64 +1,132 @@
<script setup lang="ts">
import DocsTag from '@/components/DocumentationPanel/DocsTag.vue'
import type { Doc } from '@/util/docParser'
import { computed } from 'vue'
import { computed, ref, watch } from 'vue'
const props = defineProps<{ tags: Doc.Section.Tag[] }>()
const props = defineProps<{ tags: Doc.Section.Tag[]; groupColor: string }>()
// Split Alias tags into separate ones.
const skipTags: Doc.Tag[] = ['Icon', 'TextOnly']
const tags = computed<Doc.Section.Tag[]>(() => {
return props.tags.flatMap((tag) => {
if (tag.tag === 'Alias') {
// Split Alias tags into separate ones.
return tag.body.split(/,\s*/).map((body) => ({ tag: 'Alias', body }))
} else if (skipTags.includes(tag.tag)) {
// Skip certain tags.
return []
} else {
return tag
}
})
})
interface View {
// === Sorting ===
const weight: Partial<Record<Doc.Tag, number>> = {
Group: 1000,
Unstable: 502,
Advanced: 501,
Deprecated: 500,
Alias: 100,
}
const sortedTags = computed<Doc.Section.Tag[]>(() => {
return [...tags.value].sort((a, b) => (weight[b.tag] ?? 0) - (weight[a.tag] ?? 0))
})
// === View ===
export interface View {
label: string
class?: string
style?: { [key: string]: string | number | undefined }
}
const defaultView = (tag: Doc.Tag, body: Doc.HtmlString): View => ({
label: tag + (body.length > 0 ? `=${body}` : ''),
label: (tag + (body.length > 0 ? `: ${body}` : '')).toLowerCase(),
})
const tagsMap: Partial<Record<Doc.Tag, (body: Doc.HtmlString) => View>> = {
Advanced: (_body) => ({
label: 'advanced',
style: {
'background-color': '#e89d51',
color: '#fff',
},
}),
Alias: (body) => ({ label: body }),
Deprecated: (_body) => ({
label: 'deprecated',
style: {
'background-color': '#e89d51',
color: '#fff',
},
}),
Group: (body) => ({
label: body.toLowerCase().replace('standard.base.', ''),
style: {
'background-color': props.groupColor,
color: '#fff',
},
}),
Unstable: (_body) => ({
label: 'unstable',
style: {
'background-color': '#e85252',
color: '#fff',
},
}),
}
const view = (tag: Doc.Section.Tag): View => {
const maybeView = tagsMap[tag.tag]
return maybeView ? maybeView(tag.body) : defaultView(tag.tag, tag.body)
}
const views = computed<View[]>(() => sortedTags.value.map(view))
// === Show/hide excess tags. ===
const containerRef = ref<HTMLDivElement>()
const parentOffset = computed(() => containerRef.value?.offsetTop ?? 0)
const hiddenTags = ref(0)
const tagWasHidden = () => (hiddenTags.value += 1)
const someTagsAreHidden = computed(() => hiddenTags.value > 0)
const showAllTags = ref(false)
const showEllipsis = computed(() => someTagsAreHidden.value && !showAllTags.value)
watch(
() => props.tags,
() => {
showAllTags.value = false
hiddenTags.value = 0
},
)
</script>

<template>
<div v-if="tags.length > 0" class="Tags">
<div
v-for="(tag, index) in tags"
:key="index"
:class="view(tag).class || 'tag'"
:style="view(tag).style || {}"
>
{{ view(tag).label }}
</div>
<div v-if="views.length > 0" ref="containerRef" class="Tags">
<template v-for="(v, i) in views" :key="i">
<DocsTag
class="Tag"
:view="v"
:parentOffset="parentOffset"
:forceShow="showAllTags"
@hidden="tagWasHidden"
/>
</template>
<button v-if="showEllipsis" class="Tag button" @click="() => (showAllTags = true)">…</button>
</div>
</template>

<style scoped>
.Tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-flow: row wrap;
align-items: start;
gap: 2px;
}
.tag {
display: flex;
.Tag {
height: 24px;
align-items: flex-start;
background-color: var(--enso-docs-tag-background-color);
border-radius: 4px;
padding: 1px 5px;
Expand Down
3 changes: 2 additions & 1 deletion app/gui2/src/components/DocumentationPanel/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ function getChildren(db: SuggestionDb, id: SuggestionId, kind: SuggestionKind):
if (!id) return []
const children = Array.from(db.childIdToParentId.reverseLookup(id))
return children.reduce((acc: Docs[], id: SuggestionId) => {
if (db.get(id)?.kind === kind) {
const entry = db.get(id)
if (entry?.kind === kind && !entry?.isPrivate) {
const docs = lookupDocumentation(db, id)
acc.push(docs)
}
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ watch(
const groupColors = computed(() => {
const styles: { [key: string]: string } = {}
for (let group of suggestionDb.groups) {
styles[groupColorVar(group)] = group.color ?? colorFromString(group.name.replace(/\w/g, '-'))
styles[groupColorVar(group)] = group.color ?? colorFromString(group.name)
}
return styles
})
Expand Down
2 changes: 1 addition & 1 deletion app/gui2/src/stores/suggestionDatabase/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface Group {

export function groupColorVar(group: Group | undefined): string {
if (group) {
const name = group.name.replace(/\s/g, '-')
const name = `${group.project}-${group.name}`.replace(/[^\w]/g, '-')
return `--group-color-${name}`
} else {
return '--group-color-fallback'
Expand Down
5 changes: 4 additions & 1 deletion app/gui2/stories/ComponentBrowser.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ const position = new Vec2(-329.5, 0)
<template #default="{ navigator }">
<GroupColorsWrapper>
<ComponentBrowser
:position="position"
:nodePosition="position"
:navigator="navigator"
:initialContent="''"
:sourcePort="null"
:initialCaretPosition="[0, 0]"
@finished="logEvent('finished', [])"
/>
</GroupColorsWrapper>
Expand Down
7 changes: 3 additions & 4 deletions app/gui2/stories/GroupColorsWrapper.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { groupColorVar, useSuggestionDbStore } from '@/stores/suggestionDatabase'
import { colorFromString } from '@/util/colors'
import { computed } from 'vue'
Expand All @@ -8,10 +8,9 @@ const suggestionDb = useSuggestionDbStore()
const groupColors = computed(() => {
const styles: { [key: string]: string } = {}
for (let group of suggestionDb.groups) {
const name = group.name.replace(/\s/g, '-')
let color = group.color ?? colorFromString(name)
styles[`--group-color-${name}`] = color
styles[groupColorVar(group)] = group.color ?? colorFromString(group.name)
}
styles[`--group-color-fallback`] = '#006b8a'
return styles
})
</script>
Expand Down
Loading

0 comments on commit 41cb4e3

Please sign in to comment.