Skip to content

Commit 956a495

Browse files
author
Dobromir Hristov
authored
Support custom page icons and image overrides (#417)
closes rdar://97715925 * refactor: rename NavigatorLeafIcon to TopicTypeIcon. Move location to `/components` * feat: support icon overriding * refactor: use provided in RenderJSOn svgID, for image overrides. * refactor: use `images` metadata property to override the page image * refactor: allow overriding the icon for TopicsLinkBlock
1 parent 3e82f16 commit 956a495

23 files changed

+471
-37
lines changed

src/components/DocumentationTopic.vue

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
:enhanceBackground="enhanceBackground"
1717
:shortHero="shortHero"
1818
:shouldShowLanguageSwitcher="shouldShowLanguageSwitcher"
19+
:iconOverride="references[pageIcon]"
1920
>
2021
<template #above-content>
2122
<slot name="above-hero-content" />
@@ -270,6 +271,10 @@ export default {
270271
type: Object,
271272
required: false,
272273
},
274+
pageImages: {
275+
type: Array,
276+
required: false,
277+
},
273278
},
274279
provide() {
275280
// NOTE: this is not reactive: if this.references change, the provided value
@@ -350,6 +355,15 @@ export default {
350355
|| (primaryContentSections && primaryContentSections.length)
351356
),
352357
tagName: ({ isSymbolDeprecated }) => (isSymbolDeprecated ? 'Deprecated' : 'Beta'),
358+
/**
359+
* Finds the page icon in the `pageImages` array
360+
* @param {Array} pageImages
361+
* @returns {String|null}
362+
*/
363+
pageIcon: ({ pageImages = [] }) => {
364+
const icon = pageImages.find(({ type }) => type === 'icon');
365+
return icon ? icon.identifier : null;
366+
},
353367
},
354368
methods: {
355369
normalizePath(path) {

src/components/DocumentationTopic/DocumentationHero.vue

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
:style="styles"
1818
>
1919
<div class="icon">
20-
<NavigatorLeafIcon
21-
v-if="enhanceBackground" :type="type"
20+
<TopicTypeIcon
21+
v-if="enhanceBackground"
22+
:type="type"
23+
:image-override="iconOverride"
2224
key="first" class="background-icon first-icon" with-colors
2325
/>
2426
</div>
@@ -36,14 +38,14 @@
3638

3739
<script>
3840
39-
import NavigatorLeafIcon from 'docc-render/components/Navigator/NavigatorLeafIcon.vue';
41+
import TopicTypeIcon from 'docc-render/components/TopicTypeIcon.vue';
4042
import { TopicTypes, TopicTypeAliases } from 'docc-render/constants/TopicTypes';
4143
import { HeroColorsMap, HeroColors } from 'docc-render/constants/HeroColors';
4244
import { TopicRole } from 'docc-render/constants/roles';
4345
4446
export default {
4547
name: 'DocumentationHero',
46-
components: { NavigatorLeafIcon },
48+
components: { TopicTypeIcon },
4749
props: {
4850
role: {
4951
type: String,
@@ -61,6 +63,10 @@ export default {
6163
type: Boolean,
6264
required: true,
6365
},
66+
iconOverride: {
67+
type: Object,
68+
required: false,
69+
},
6470
},
6571
computed: {
6672
// get the alias, if any, and fallback to the `teal` color

src/components/DocumentationTopic/TopicLinkBlockIcon.vue

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@
99
-->
1010

1111
<template>
12-
<div class="topic-icon-wrapper" v-if="icon">
13-
<component :is="icon" class="topic-icon" />
14-
</div>
12+
<IconOverrideProvider
13+
:imageOverride="imageOverride"
14+
#default="{ shouldUseOverride, themeId, iconUrl }"
15+
>
16+
<div class="topic-icon-wrapper" v-if="icon || shouldUseOverride">
17+
<SVGIcon
18+
v-if="shouldUseOverride"
19+
v-bind="{ themeId, iconUrl }"
20+
class="topic-icon"
21+
/>
22+
<component v-else :is="icon" class="topic-icon" />
23+
</div>
24+
</IconOverrideProvider>
1525
</template>
1626

1727
<script>
@@ -21,7 +31,9 @@ import ApiCollectionIcon from 'theme/components/Icons/APIReferenceIcon.vue';
2131
import EndpointIcon from 'theme/components/Icons/EndpointIcon.vue';
2232
import PathIcon from 'theme/components/Icons/PathIcon.vue';
2333
import TutorialIcon from 'theme/components/Icons/TutorialIcon.vue';
34+
import SVGIcon from 'docc-render/components/SVGIcon.vue';
2435
import { TopicRole } from 'docc-render/constants/roles';
36+
import IconOverrideProvider from 'docc-render/components/IconOverrideProvider.vue';
2537
2638
const TopicRoleIcons = {
2739
[TopicRole.article]: ArticleIcon,
@@ -36,11 +48,16 @@ const TopicRoleIcons = {
3648
};
3749
3850
export default {
51+
components: { IconOverrideProvider, SVGIcon },
3952
props: {
4053
role: {
4154
type: String,
4255
required: true,
4356
},
57+
imageOverride: {
58+
type: Object,
59+
default: null,
60+
},
4461
},
4562
4663
computed: {

src/components/DocumentationTopic/TopicsLinkBlock.vue

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
class="link"
1818
ref="apiChangesDiff"
1919
>
20-
<TopicLinkBlockIcon v-if="topic.role && !change" :role="topic.role" />
20+
<TopicLinkBlockIcon
21+
v-if="topic.role && !change"
22+
:role="topic.role"
23+
:imageOverride="references[iconOverride]"
24+
/>
2125
<DecoratedTopicTitle v-if="topic.fragments" :tokens="topic.fragments" />
2226
<WordBreak v-else :tag="titleTag">{{ topic.title }}</WordBreak>
2327
<span v-if="change" class="visuallyhidden">- {{ changeName }}</span>
@@ -66,7 +70,8 @@ import WordBreak from 'docc-render/components/WordBreak.vue';
6670
import ContentNode from 'docc-render/components/DocumentationTopic/ContentNode.vue';
6771
import TopicLinkBlockIcon from 'docc-render/components/DocumentationTopic/TopicLinkBlockIcon.vue';
6872
import DecoratedTopicTitle from 'docc-render/components/DocumentationTopic/DecoratedTopicTitle.vue';
69-
import ConditionalConstraints from 'docc-render/components/DocumentationTopic/ConditionalConstraints.vue';
73+
import ConditionalConstraints
74+
from 'docc-render/components/DocumentationTopic/ConditionalConstraints.vue';
7075
import RequirementMetadata
7176
7277
from 'docc-render/components/DocumentationTopic/Description/RequirementMetadata.vue';
@@ -98,7 +103,7 @@ export default {
98103
RequirementMetadata,
99104
ConditionalConstraints,
100105
},
101-
inject: ['store'],
106+
inject: ['store', 'references'],
102107
mixins: [getAPIChanges, APIChangesMultipleLines],
103108
constants: {
104109
ReferenceType,
@@ -208,6 +213,10 @@ export default {
208213
),
209214
// pick only the first available tag
210215
tags: ({ topic }) => (topic.tags || []).slice(0, 1),
216+
iconOverride: ({ topic: { images = [] } }) => {
217+
const icon = images.find(({ type }) => type === 'icon');
218+
return icon ? icon.identifier : null;
219+
},
211220
},
212221
};
213222
</script>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!--
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
-->
10+
11+
<script>
12+
export default {
13+
name: 'IconOverrideProvider',
14+
props: {
15+
imageOverride: {
16+
type: Object,
17+
default: null,
18+
},
19+
},
20+
computed: {
21+
imageOverrideVariant: ({ imageOverride }) => (imageOverride ? imageOverride.variants[0] : null),
22+
iconUrl: ({ imageOverrideVariant }) => imageOverrideVariant && imageOverrideVariant.url,
23+
themeId: ({ imageOverrideVariant }) => imageOverrideVariant && imageOverrideVariant.svgID,
24+
shouldUseOverride: ({ iconUrl, themeId }) => !!(iconUrl && themeId),
25+
},
26+
render() {
27+
const { shouldUseOverride, iconUrl, themeId } = this;
28+
return this.$scopedSlots.default({
29+
shouldUseOverride,
30+
iconUrl,
31+
themeId,
32+
});
33+
},
34+
};
35+
</script>

src/components/Navigator.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
:api-changes="apiChanges"
2828
:allow-hiding="allowHiding"
2929
:enableQuickNavigation="enableQuickNavigation"
30+
:navigator-references="navigatorReferences"
3031
@close="$emit('close')"
3132
/>
3233
<NavigatorCardInner v-else class="loading-placeholder">
@@ -55,6 +56,7 @@ import { getSetting } from 'docc-render/utils/theme-settings';
5556
* @property {number} uid - generated UID
5657
* @property {string} title - title of symbol
5758
* @property {string} type - symbol type, used for the icon
59+
* @property {string} icon - an image reference to override the type icon
5860
* @property {array} abstract - symbol abstract
5961
* @property {string} path - path to page, used in navigation
6062
* @property {number} parent - parent UID
@@ -101,6 +103,10 @@ export default {
101103
type: Object,
102104
default: () => {},
103105
},
106+
navigatorReferences: {
107+
type: Object,
108+
default: () => {},
109+
},
104110
scrollLockID: {
105111
type: String,
106112
default: '',

src/components/Navigator/NavigatorCard.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
:api-change="apiChangesObject[item.path]"
6666
:isFocused="focusedIndex === index"
6767
:enableFocus="!externalFocusChange"
68+
:navigator-references="navigatorReferences"
6869
@toggle="toggle"
6970
@toggle-full="toggleFullTree"
7071
@toggle-siblings="toggleSiblings"
@@ -253,6 +254,10 @@ export default {
253254
type: Boolean,
254255
default: true,
255256
},
257+
navigatorReferences: {
258+
type: Object,
259+
default: () => {},
260+
},
256261
},
257262
mixins: [
258263
keyboardNavigation,

src/components/Navigator/NavigatorCardItem.vue

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@
4545
/>
4646
</button>
4747
</div>
48-
<NavigatorLeafIcon
48+
<TopicTypeIcon
4949
v-if="!isGroupMarker && !apiChange"
5050
:type="item.type"
51+
:image-override="item.icon ? navigatorReferences[item.icon] : null"
5152
class="navigator-icon"
5253
/>
5354
<span
@@ -93,7 +94,7 @@
9394

9495
<script>
9596
import InlineChevronRightIcon from 'theme/components/Icons/InlineChevronRightIcon.vue';
96-
import NavigatorLeafIcon from 'docc-render/components/Navigator/NavigatorLeafIcon.vue';
97+
import TopicTypeIcon from 'docc-render/components/TopicTypeIcon.vue';
9798
import HighlightMatches from 'docc-render/components/Navigator/HighlightMatches.vue';
9899
import Reference from 'docc-render/components/ContentNode/Reference.vue';
99100
import Badge from 'docc-render/components/Badge.vue';
@@ -111,7 +112,7 @@ export default {
111112
],
112113
components: {
113114
HighlightMatches,
114-
NavigatorLeafIcon,
115+
TopicTypeIcon,
115116
InlineChevronRightIcon,
116117
Reference,
117118
Badge,
@@ -154,6 +155,10 @@ export default {
154155
type: Boolean,
155156
default: true,
156157
},
158+
navigatorReferences: {
159+
type: Object,
160+
default: () => ({}),
161+
},
157162
},
158163
idState() {
159164
return {

src/components/Navigator/NavigatorDataProvider.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default {
4040
navigationIndex: {
4141
[Language.swift.key.url]: [],
4242
},
43+
navigationReferences: {},
4344
diffs: null,
4445
};
4546
},
@@ -73,8 +74,9 @@ export default {
7374
async fetchIndexData() {
7475
try {
7576
this.isFetching = true;
76-
const { interfaceLanguages } = await fetchIndexPathsData();
77+
const { interfaceLanguages, references } = await fetchIndexPathsData();
7778
this.navigationIndex = Object.freeze(interfaceLanguages);
79+
this.navigationReferences = Object.freeze(references);
7880
} catch (e) {
7981
this.errorFetching = true;
8082
} finally {
@@ -89,6 +91,7 @@ export default {
8991
errorFetching: this.errorFetching,
9092
isFetchingAPIChanges: this.isFetchingAPIChanges,
9193
apiChanges: this.diffs,
94+
references: this.navigationReferences,
9295
});
9396
},
9497
};

src/components/SVGIcon.vue

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,30 @@
1313
aria-hidden="true"
1414
class="svg-icon"
1515
xmlns="http://www.w3.org/2000/svg"
16+
xmlns:xlink="http://www.w3.org/1999/xlink"
1617
>
17-
<slot />
18+
<use v-if="themeOverrideURL" :href="`${themeOverrideURL}#${themeId}`" />
19+
<slot v-else />
1820
</svg>
1921
</template>
2022

2123
<script>
22-
export default { name: 'SVGIcon' };
24+
export default {
25+
name: 'SVGIcon',
26+
props: {
27+
themeId: {
28+
type: String,
29+
required: false,
30+
},
31+
iconUrl: {
32+
type: String,
33+
default: null,
34+
},
35+
},
36+
computed: {
37+
themeOverrideURL: ({ iconUrl }) => iconUrl,
38+
},
39+
};
2340
</script>
2441

2542
<style scoped lang="scss">

0 commit comments

Comments
 (0)