@lvce-editor/extension-search-view 7.20.4 → 7.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2904,2039 +2904,2048 @@ const getActions = () => {
2904
2904
  }];
2905
2905
  };
2906
2906
 
2907
- const Tab = 2;
2908
- const Enter = 3;
2909
- const Escape = 8;
2910
- const Space = 9;
2911
- const PageUp = 10;
2912
- const PageDown = 11;
2913
- const End = 255;
2914
- const Home = 12;
2915
- const UpArrow = 14;
2916
- const DownArrow = 16;
2907
+ const getComponentState = uid => {
2908
+ return get(uid).newState;
2909
+ };
2917
2910
 
2918
- const CtrlCmd = 1 << 11 >>> 0;
2911
+ const mergeClassNames = (...classNames) => {
2912
+ return classNames.filter(Boolean).join(' ');
2913
+ };
2919
2914
 
2920
- const FocusExtensions = 15;
2921
- const FocusExtensionsInput = 7000;
2915
+ const text = data => {
2916
+ return {
2917
+ childCount: 0,
2918
+ text: data,
2919
+ type: Text
2920
+ };
2921
+ };
2922
2922
 
2923
- const getKeyBindings = () => {
2924
- return [{
2925
- command: 'Extensions.closeSuggest',
2926
- key: Escape,
2927
- when: FocusExtensionsInput
2928
- }, {
2929
- command: 'Extensions.acceptCompletion',
2930
- key: Enter,
2931
- when: FocusExtensionsInput
2932
- }, {
2933
- command: 'Extensions.acceptCompletion',
2934
- key: Tab,
2935
- when: FocusExtensionsInput
2936
- }, {
2937
- command: 'Extensions.selectPreviousCompletion',
2938
- key: UpArrow,
2939
- when: FocusExtensionsInput
2940
- }, {
2941
- command: 'Extensions.selectNextCompletion',
2942
- key: DownArrow,
2943
- when: FocusExtensionsInput
2944
- }, {
2945
- command: 'Extensions.openSuggest',
2946
- key: CtrlCmd | Space,
2947
- when: FocusExtensionsInput
2948
- }, {
2949
- command: 'Extensions.focusFirst',
2950
- key: Home,
2951
- when: FocusExtensions
2952
- }, {
2953
- command: 'Extensions.focusLast',
2954
- key: End,
2955
- when: FocusExtensions
2956
- }, {
2957
- command: 'Extensions.focusPreviousPage',
2958
- key: PageUp,
2959
- when: FocusExtensions
2960
- }, {
2961
- command: 'Extensions.focusNextPage',
2962
- key: PageDown,
2963
- when: FocusExtensions
2964
- }, {
2965
- command: 'Extensions.focusPrevious',
2966
- key: UpArrow,
2967
- when: FocusExtensions
2968
- }, {
2969
- command: 'Extensions.focusNext',
2970
- key: DownArrow,
2971
- when: FocusExtensions
2972
- }, {
2973
- command: 'Extensions.handleClickCurrentButKeepFocus',
2974
- key: Space,
2975
- when: FocusExtensions
2976
- }, {
2977
- command: 'Extensions.handleClickCurrent',
2978
- key: Enter,
2979
- when: FocusExtensions
2980
- }, {
2981
- command: 'Extensions.toggleSuggest',
2982
- key: CtrlCmd | Space,
2983
- when: FocusExtensions
2984
- }, {
2985
- command: 'Extensions.scrollDown',
2986
- key: CtrlCmd | DownArrow,
2987
- when: FocusExtensions
2988
- }];
2923
+ new Set(Object.values(VirtualDomElements));
2924
+
2925
+ const SetText = 1;
2926
+ const Replace = 2;
2927
+ const SetAttribute = 3;
2928
+ const RemoveAttribute = 4;
2929
+ const Add = 6;
2930
+ const NavigateChild = 7;
2931
+ const NavigateParent = 8;
2932
+ const RemoveChild = 9;
2933
+ const NavigateSibling = 10;
2934
+ const SetReferenceNodeUid = 11;
2935
+
2936
+ const isKey = key => {
2937
+ return key !== 'type' && key !== 'childCount';
2989
2938
  };
2990
2939
 
2991
- const Separator = 1;
2992
- const None$1 = 0;
2993
- const SubMenu = 4;
2994
- const Disabled = 5;
2940
+ const getKeys = node => {
2941
+ const keys = Object.keys(node).filter(isKey);
2942
+ return keys;
2943
+ };
2995
2944
 
2996
- const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
2997
- const getEnablementFlags = (disabled, status) => {
2998
- if (status && nonEnableableStatuses.includes(status)) {
2945
+ const arrayToTree = nodes => {
2946
+ const result = [];
2947
+ let i = 0;
2948
+ while (i < nodes.length) {
2949
+ const node = nodes[i];
2950
+ const {
2951
+ children,
2952
+ nodesConsumed
2953
+ } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
2954
+ result.push({
2955
+ node,
2956
+ children
2957
+ });
2958
+ i += 1 + nodesConsumed;
2959
+ }
2960
+ return result;
2961
+ };
2962
+ const getChildrenWithCount = (nodes, startIndex, childCount) => {
2963
+ if (childCount === 0) {
2999
2964
  return {
3000
- disable: Disabled,
3001
- enable: Disabled
2965
+ children: [],
2966
+ nodesConsumed: 0
3002
2967
  };
3003
2968
  }
3004
- const isDisabled = status === Disabled$1 || status === undefined && disabled;
2969
+ const children = [];
2970
+ let i = startIndex;
2971
+ let remaining = childCount;
2972
+ let totalConsumed = 0;
2973
+ while (remaining > 0 && i < nodes.length) {
2974
+ const node = nodes[i];
2975
+ const nodeChildCount = node.childCount || 0;
2976
+ const {
2977
+ children: nodeChildren,
2978
+ nodesConsumed
2979
+ } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
2980
+ children.push({
2981
+ node,
2982
+ children: nodeChildren
2983
+ });
2984
+ const nodeSize = 1 + nodesConsumed;
2985
+ i += nodeSize;
2986
+ totalConsumed += nodeSize;
2987
+ remaining--;
2988
+ }
3005
2989
  return {
3006
- disable: isDisabled ? Disabled : None$1,
3007
- enable: isDisabled ? None$1 : Disabled
2990
+ children,
2991
+ nodesConsumed: totalConsumed
3008
2992
  };
3009
2993
  };
3010
- const getMenuEntriesList = (builtin, disabled = false, status) => {
3011
- const enablementFlags = getEnablementFlags(disabled, status);
3012
- return [{
3013
- command: 'Extensions.enable',
3014
- flags: enablementFlags.enable,
3015
- id: 'enable',
3016
- label: enable$2()
3017
- }, {
3018
- command: 'Extensions.enableWorkspace',
3019
- flags: enablementFlags.enable,
3020
- id: 'enableWorkspace',
3021
- label: enableWorkspace$1()
3022
- }, {
3023
- command: '',
3024
- flags: Separator,
3025
- id: 'separator1',
3026
- label: ''
3027
- }, {
3028
- command: 'Extensions.disable',
3029
- flags: enablementFlags.disable,
3030
- id: 'disable',
3031
- label: disable$2()
3032
- }, {
3033
- command: 'Extensions.disableWorkspace',
3034
- flags: enablementFlags.disable,
3035
- id: 'disableWorkspace',
3036
- label: disableWorkspace$1()
3037
- }, {
3038
- command: '',
3039
- flags: Separator,
3040
- id: 'separator2',
3041
- label: ''
3042
- }, {
3043
- command: 'Extensions.installAnotherVersion',
3044
- flags: Disabled,
3045
- id: 'installAnotherVersion',
3046
- label: installAnotherVersion$1()
3047
- }, {
3048
- command: 'Extensions.copyExtensionInfo',
3049
- flags: None$1,
3050
- id: 'copy',
3051
- label: copy()
3052
- }, {
3053
- command: 'Extensions.copyExtensionId',
3054
- flags: None$1,
3055
- id: 'copyExtensionId',
3056
- label: copyExtensionId$1()
3057
- }];
2994
+
2995
+ const compareNodes = (oldNode, newNode) => {
2996
+ // Check if node type changed - return null to signal incompatible nodes
2997
+ // (caller should handle this with a Replace operation)
2998
+ if (oldNode.type !== newNode.type) {
2999
+ return null;
3000
+ }
3001
+ const patches = [];
3002
+ // Handle reference nodes - special handling for uid changes
3003
+ if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3004
+ patches.push({
3005
+ type: SetReferenceNodeUid,
3006
+ uid: newNode.uid
3007
+ });
3008
+ }
3009
+ // Handle text nodes
3010
+ if (oldNode.type === Text && newNode.type === Text) {
3011
+ if (oldNode.text !== newNode.text) {
3012
+ patches.push({
3013
+ type: SetText,
3014
+ value: newNode.text
3015
+ });
3016
+ }
3017
+ return patches;
3018
+ }
3019
+ // Compare attributes
3020
+ const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3021
+ const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3022
+ // Check for attribute changes
3023
+ for (const key of newKeys) {
3024
+ if (oldNode[key] !== newNode[key]) {
3025
+ patches.push({
3026
+ type: SetAttribute,
3027
+ key,
3028
+ value: newNode[key]
3029
+ });
3030
+ }
3031
+ }
3032
+ // Check for removed attributes
3033
+ for (const key of oldKeys) {
3034
+ if (!Object.hasOwn(newNode, key)) {
3035
+ patches.push({
3036
+ type: RemoveAttribute,
3037
+ key
3038
+ });
3039
+ }
3040
+ }
3041
+ return patches;
3058
3042
  };
3059
3043
 
3060
- const getMenuEntriesFilter = () => {
3061
- return [{
3062
- command: 'Extensions.filterByFeatured',
3063
- flags: None$1,
3064
- id: 'filterByFeatured',
3065
- label: featured()
3066
- }, {
3067
- command: 'Extensions.filterByMcpServers',
3068
- flags: None$1,
3069
- id: 'filterByMcpServers',
3070
- label: mcpServers()
3071
- }, {
3072
- command: 'Extensions.filterByMostPopular',
3073
- flags: None$1,
3074
- id: 'filterByMostPopular',
3075
- label: mostPopular()
3076
- }, {
3077
- command: 'Extensions.filterByRecentlyPublished',
3078
- flags: None$1,
3079
- id: 'filterByRecentlyPublished',
3080
- label: recentlyPublished()
3081
- }, {
3082
- command: 'Extensions.filterByRecommended',
3083
- flags: None$1,
3084
- id: 'filterByRecommended',
3085
- label: recommended()
3086
- }, {
3087
- command: '',
3088
- flags: Separator,
3089
- id: 'separator1',
3090
- label: ''
3091
- }, {
3092
- command: 'SearchExtensions.filterByCategory',
3093
- flags: SubMenu,
3094
- id: 'filterByCategory',
3095
- label: category()
3096
- }, {
3097
- command: 'Extensions.filterByInstalled',
3098
- flags: None$1,
3099
- id: 'filterByInstalled',
3100
- label: installed()
3101
- }, {
3102
- command: 'Extensions.filterByUpdates',
3103
- flags: None$1,
3104
- id: 'filterByUpdates',
3105
- label: updates()
3106
- }, {
3107
- command: 'Extensions.filterByBuiltin',
3108
- flags: None$1,
3109
- id: 'filterByBuiltin',
3110
- label: builtIn()
3111
- }, {
3112
- command: 'Extensions.filterByLinked',
3113
- flags: None$1,
3114
- id: 'filterByLinked',
3115
- label: linked()
3116
- }, {
3117
- command: 'Extensions.filterByEnabled',
3118
- flags: None$1,
3119
- id: 'filterByEnabled',
3120
- label: enabled()
3121
- }, {
3122
- command: 'Extensions.filterByDisabled',
3123
- flags: None$1,
3124
- id: 'filterByDisabled',
3125
- label: disabled()
3126
- }, {
3127
- command: 'Extensions.filterByWorkspaceUnsupported',
3128
- flags: None$1,
3129
- id: 'filterByWorkspaceUnsupported',
3130
- label: workspaceUnsupported()
3131
- }, {
3132
- command: '',
3133
- flags: Separator,
3134
- id: 'separator2',
3135
- label: ''
3136
- }, {
3137
- command: 'SearchExtensions.filterBySortBy',
3138
- flags: SubMenu,
3139
- id: 'filterBySortBy',
3140
- label: sortBy()
3141
- }];
3044
+ const treeToArray = node => {
3045
+ const result = [];
3046
+ const stack = [node];
3047
+ while (stack.length > 0) {
3048
+ const current = stack.pop();
3049
+ result.push(current.node);
3050
+ for (let i = current.children.length - 1; i >= 0; i--) {
3051
+ stack.push(current.children[i]);
3052
+ }
3053
+ }
3054
+ return result;
3142
3055
  };
3143
3056
 
3144
- const getMenuEntries2 = (state, props) => {
3145
- const {
3146
- menuId
3147
- } = props;
3148
- switch (menuId) {
3149
- case ExtensionSearchFilter:
3150
- return getMenuEntriesFilter();
3151
- default:
3152
- return getMenuEntriesList(props.builtin, props.disabled, props.status);
3057
+ const navigateToChild = (patches, currentChildIndex, index) => {
3058
+ if (currentChildIndex === -1) {
3059
+ patches.push({
3060
+ type: NavigateChild,
3061
+ index
3062
+ });
3063
+ return index;
3153
3064
  }
3065
+ if (currentChildIndex !== index) {
3066
+ patches.push({
3067
+ type: NavigateSibling,
3068
+ index
3069
+ });
3070
+ }
3071
+ return index;
3154
3072
  };
3155
-
3156
- const getMenuIds = () => {
3157
- return [ManageExtension, ExtensionSearchFilter];
3073
+ const navigateToParent = (patches, currentChildIndex) => {
3074
+ if (currentChildIndex >= 0) {
3075
+ patches.push({
3076
+ type: NavigateParent
3077
+ });
3078
+ }
3079
+ return -1;
3158
3080
  };
3159
-
3160
- const handleBlur = state => {
3161
- return {
3162
- ...state,
3163
- focus: None$2,
3164
- suggestOpen: false
3165
- };
3081
+ const addTree = (newNode, patches) => {
3082
+ patches.push({
3083
+ type: Add,
3084
+ nodes: treeToArray(newNode)
3085
+ });
3166
3086
  };
3167
-
3168
- const getExtensionDetailUri = extensionId => {
3169
- return `extension-detail://${extensionId}`;
3087
+ const replaceTree = (newNode, patches) => {
3088
+ patches.push({
3089
+ type: Replace,
3090
+ nodes: treeToArray(newNode)
3091
+ });
3170
3092
  };
3171
-
3172
- const openUri = async uri => {
3173
- return openUri$1(uri);
3093
+ const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3094
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3095
+ if (nodePatches === null) {
3096
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3097
+ replaceTree(newNode, patches);
3098
+ return nextChildIndex;
3099
+ }
3100
+ const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3101
+ if (nodePatches.length === 0 && !hasChildrenToCompare) {
3102
+ return currentChildIndex;
3103
+ }
3104
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3105
+ if (nodePatches.length > 0) {
3106
+ patches.push(...nodePatches);
3107
+ }
3108
+ if (hasChildrenToCompare) {
3109
+ diffChildren(oldNode.children, newNode.children, patches);
3110
+ }
3111
+ return nextChildIndex;
3174
3112
  };
3175
-
3176
- const selectIndex = (state, index) => {
3177
- return {
3178
- ...state,
3179
- focusedIndex: index
3180
- };
3113
+ const diffRootNode = (oldNode, newNode, patches) => {
3114
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3115
+ if (nodePatches === null) {
3116
+ replaceTree(newNode, patches);
3117
+ return;
3118
+ }
3119
+ if (nodePatches.length > 0) {
3120
+ patches.push(...nodePatches);
3121
+ }
3122
+ if (oldNode.children.length > 0 || newNode.children.length > 0) {
3123
+ diffChildren(oldNode.children, newNode.children, patches);
3124
+ }
3181
3125
  };
3182
-
3183
- const handleClick = async (state, index) => {
3184
- const {
3185
- items,
3186
- minLineY
3187
- } = state;
3188
- const actualIndex = index + minLineY;
3189
- if (actualIndex < 0 || actualIndex >= items.length) {
3190
- return {
3191
- ...state,
3192
- focus: List$2,
3193
- focusedIndex: -1
3194
- };
3126
+ const diffChildren = (oldChildren, newChildren, patches) => {
3127
+ const maxLength = Math.max(oldChildren.length, newChildren.length);
3128
+ let currentChildIndex = -1;
3129
+ const indicesToRemove = [];
3130
+ for (let i = 0; i < maxLength; i++) {
3131
+ const oldNode = oldChildren[i];
3132
+ const newNode = newChildren[i];
3133
+ if (!oldNode && !newNode) {
3134
+ continue;
3135
+ }
3136
+ if (!oldNode) {
3137
+ currentChildIndex = navigateToParent(patches, currentChildIndex);
3138
+ addTree(newNode, patches);
3139
+ continue;
3140
+ }
3141
+ if (!newNode) {
3142
+ indicesToRemove.push(i);
3143
+ continue;
3144
+ }
3145
+ currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3195
3146
  }
3196
- const extension = items[actualIndex];
3197
- const uri = getExtensionDetailUri(extension.id);
3198
- await openUri(uri);
3199
- const partialNewState = selectIndex(state, actualIndex);
3200
- const newState = {
3201
- ...partialNewState,
3202
- focus: List$2
3203
- };
3204
- return newState;
3147
+ navigateToParent(patches, currentChildIndex);
3148
+ for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3149
+ patches.push({
3150
+ type: RemoveChild,
3151
+ index: indicesToRemove[j]
3152
+ });
3153
+ }
3154
+ };
3155
+ const diffTrees = (oldTree, newTree, patches, path) => {
3156
+ if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3157
+ diffRootNode(oldTree[0], newTree[0], patches);
3158
+ return;
3159
+ }
3160
+ diffChildren(oldTree, newTree, patches);
3205
3161
  };
3206
3162
 
3207
- const mergeClassNames = (...classNames) => {
3208
- return classNames.filter(Boolean).join(' ');
3163
+ const removeTrailingNavigationPatches = patches => {
3164
+ while (patches.length > 0) {
3165
+ const patch = patches.at(-1);
3166
+ if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3167
+ break;
3168
+ }
3169
+ patches.pop();
3170
+ }
3171
+ return patches;
3209
3172
  };
3210
3173
 
3211
- const text = data => {
3212
- return {
3213
- childCount: 0,
3214
- text: data,
3215
- type: Text
3216
- };
3174
+ const diffTree = (oldNodes, newNodes) => {
3175
+ // Step 1: Convert flat arrays to tree structures
3176
+ const oldTree = arrayToTree(oldNodes);
3177
+ const newTree = arrayToTree(newNodes);
3178
+ // Step 3: Compare the trees
3179
+ const patches = [];
3180
+ diffTrees(oldTree, newTree, patches, []);
3181
+ // Remove trailing navigation patches since they serve no purpose
3182
+ return removeTrailingNavigationPatches(patches);
3217
3183
  };
3218
3184
 
3219
- new Set(Object.values(VirtualDomElements));
3220
-
3221
- const SetText = 1;
3222
- const Replace = 2;
3223
- const SetAttribute = 3;
3224
- const RemoveAttribute = 4;
3225
- const Add = 6;
3226
- const NavigateChild = 7;
3227
- const NavigateParent = 8;
3228
- const RemoveChild = 9;
3229
- const NavigateSibling = 10;
3230
- const SetReferenceNodeUid = 11;
3231
-
3232
- const isKey = key => {
3233
- return key !== 'type' && key !== 'childCount';
3234
- };
3185
+ const Actions = 'Actions';
3186
+ const ExtensionActions = 'ExtensionActions';
3187
+ const ExtensionActionButton = 'ExtensionActionButton';
3188
+ const ExtensionActive = 'ExtensionActive';
3189
+ const ExtensionHeader = 'ExtensionHeader';
3190
+ const ExtensionListItem = 'ExtensionListItem';
3191
+ const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
3192
+ const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
3193
+ const ExtensionListItemDescription = 'ExtensionListItemDescription';
3194
+ const ExtensionListItemDetail = 'ExtensionListItemDetail';
3195
+ const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
3196
+ const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
3197
+ const ExtensionListItemFooter = 'ExtensionListItemFooter';
3198
+ const ExtensionListItemIcon = 'ExtensionListItemIcon';
3199
+ const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
3200
+ const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
3201
+ const ExtensionListItemName = 'ExtensionListItemName';
3202
+ const ExtensionListItemRating = 'ExtensionListItemRating';
3203
+ const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
3204
+ const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
3205
+ const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
3206
+ const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
3207
+ const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
3208
+ const Extensions$1 = 'Extensions';
3209
+ const FocusOutline = 'FocusOutline';
3210
+ const IconButton = 'IconButton';
3211
+ const List$1 = 'List';
3212
+ const ListItems = 'ListItems';
3213
+ const MaskIcon = 'MaskIcon';
3214
+ const MultilineInputBox = 'MultilineInputBox';
3215
+ const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
3216
+ const ScrollBar = 'ScrollBar';
3217
+ const ScrollBarSmall = 'ScrollBarSmall';
3218
+ const ScrollBarThumb = 'ScrollBarThumb';
3219
+ const ScrollBarThumbActive = 'ScrollBarThumbActive';
3220
+ const SearchField = 'SearchField';
3221
+ const SearchFieldButton = 'SearchFieldButton';
3222
+ const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
3223
+ const SearchFieldButtons = 'SearchFieldButtons';
3224
+ const SearchFieldContainer = 'SearchFieldContainer';
3225
+ const Viewlet = 'Viewlet';
3235
3226
 
3236
- const getKeys = node => {
3237
- const keys = Object.keys(node).filter(isKey);
3238
- return keys;
3239
- };
3227
+ const ComboBox = 'combobox';
3228
+ const Image = 'img';
3229
+ const List = 'list';
3230
+ const ListBox = 'listbox';
3231
+ const ListItem = 'listitem';
3232
+ const None$1 = 'none';
3233
+ const Option = 'option';
3234
+ const ToolBar = 'toolbar';
3240
3235
 
3241
- const arrayToTree = nodes => {
3242
- const result = [];
3243
- let i = 0;
3244
- while (i < nodes.length) {
3245
- const node = nodes[i];
3246
- const {
3247
- children,
3248
- nodesConsumed
3249
- } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
3250
- result.push({
3251
- node,
3252
- children
3253
- });
3254
- i += 1 + nodesConsumed;
3255
- }
3256
- return result;
3257
- };
3258
- const getChildrenWithCount = (nodes, startIndex, childCount) => {
3259
- if (childCount === 0) {
3260
- return {
3261
- children: [],
3262
- nodesConsumed: 0
3263
- };
3236
+ const getCompletionLabelVirtualDom = (label, highlights) => {
3237
+ const dom = [];
3238
+ let position = 0;
3239
+ for (let i = 0; i < highlights.length; i += 2) {
3240
+ const start = highlights[i];
3241
+ const end = highlights[i + 1];
3242
+ if (position < start) {
3243
+ dom.push(text(label.slice(position, start)));
3244
+ }
3245
+ dom.push({
3246
+ childCount: 1,
3247
+ className: ExtensionSearchCompletionHighlight,
3248
+ name: label,
3249
+ type: Span
3250
+ }, text(label.slice(start, end)));
3251
+ position = end;
3264
3252
  }
3265
- const children = [];
3266
- let i = startIndex;
3267
- let remaining = childCount;
3268
- let totalConsumed = 0;
3269
- while (remaining > 0 && i < nodes.length) {
3270
- const node = nodes[i];
3271
- const nodeChildCount = node.childCount || 0;
3272
- const {
3273
- children: nodeChildren,
3274
- nodesConsumed
3275
- } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
3276
- children.push({
3277
- node,
3278
- children: nodeChildren
3279
- });
3280
- const nodeSize = 1 + nodesConsumed;
3281
- i += nodeSize;
3282
- totalConsumed += nodeSize;
3283
- remaining--;
3253
+ if (position < label.length) {
3254
+ dom.push(text(label.slice(position)));
3284
3255
  }
3285
- return {
3286
- children,
3287
- nodesConsumed: totalConsumed
3288
- };
3256
+ return dom;
3289
3257
  };
3290
3258
 
3291
- const compareNodes = (oldNode, newNode) => {
3292
- // Check if node type changed - return null to signal incompatible nodes
3293
- // (caller should handle this with a Replace operation)
3294
- if (oldNode.type !== newNode.type) {
3295
- return null;
3296
- }
3297
- const patches = [];
3298
- // Handle reference nodes - special handling for uid changes
3299
- if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3300
- patches.push({
3301
- type: SetReferenceNodeUid,
3302
- uid: newNode.uid
3303
- });
3304
- }
3305
- // Handle text nodes
3306
- if (oldNode.type === Text && newNode.type === Text) {
3307
- if (oldNode.text !== newNode.text) {
3308
- patches.push({
3309
- type: SetText,
3310
- value: newNode.text
3311
- });
3312
- }
3313
- return patches;
3314
- }
3315
- // Compare attributes
3316
- const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3317
- const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3318
- // Check for attribute changes
3319
- for (const key of newKeys) {
3320
- if (oldNode[key] !== newNode[key]) {
3321
- patches.push({
3322
- type: SetAttribute,
3323
- key,
3324
- value: newNode[key]
3325
- });
3259
+ const getRootNodeCount = nodes => {
3260
+ let count = 0;
3261
+ const remainingChildCounts = [];
3262
+ for (const node of nodes) {
3263
+ while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
3264
+ remainingChildCounts.pop();
3326
3265
  }
3327
- }
3328
- // Check for removed attributes
3329
- for (const key of oldKeys) {
3330
- if (!Object.hasOwn(newNode, key)) {
3331
- patches.push({
3332
- type: RemoveAttribute,
3333
- key
3334
- });
3266
+ if (remainingChildCounts.length === 0) {
3267
+ count++;
3268
+ } else {
3269
+ const lastIndex = remainingChildCounts.length - 1;
3270
+ remainingChildCounts[lastIndex]--;
3335
3271
  }
3272
+ remainingChildCounts.push(node.childCount);
3336
3273
  }
3337
- return patches;
3274
+ return count;
3338
3275
  };
3339
-
3340
- const treeToArray = node => {
3341
- const result = [];
3342
- const stack = [node];
3343
- while (stack.length > 0) {
3344
- const current = stack.pop();
3345
- result.push(current.node);
3346
- for (let i = current.children.length - 1; i >= 0; i--) {
3347
- stack.push(current.children[i]);
3348
- }
3349
- }
3350
- return result;
3276
+ const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
3277
+ const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
3278
+ const focused = index === focusedIndex;
3279
+ return [{
3280
+ ariaSelected: focused,
3281
+ childCount: getRootNodeCount(labelDom),
3282
+ className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
3283
+ id: `ExtensionSearchCompletion-${index}`,
3284
+ name: item.label,
3285
+ onPointerDown: HandlePointerDown,
3286
+ role: Option,
3287
+ type: Button$2
3288
+ }, ...labelDom];
3351
3289
  };
3352
3290
 
3353
- const navigateToChild = (patches, currentChildIndex, index) => {
3354
- if (currentChildIndex === -1) {
3355
- patches.push({
3356
- type: NavigateChild,
3357
- index
3358
- });
3359
- return index;
3360
- }
3361
- if (currentChildIndex !== index) {
3362
- patches.push({
3363
- type: NavigateSibling,
3364
- index
3365
- });
3366
- }
3367
- return index;
3291
+ const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
3292
+ return [{
3293
+ ariaLabel: 'Extension search completions',
3294
+ childCount: items.length,
3295
+ className: ExtensionSearchCompletionWidget,
3296
+ id: 'ExtensionSearchCompletions',
3297
+ role: ListBox,
3298
+ type: Div
3299
+ }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
3368
3300
  };
3369
- const navigateToParent = (patches, currentChildIndex) => {
3370
- if (currentChildIndex >= 0) {
3371
- patches.push({
3372
- type: NavigateParent
3373
- });
3301
+
3302
+ const Focusable = 0;
3303
+
3304
+ const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
3305
+ const getClassName = enabled => {
3306
+ if (enabled) {
3307
+ return SearchFieldButton;
3374
3308
  }
3375
- return -1;
3376
- };
3377
- const addTree = (newNode, patches) => {
3378
- patches.push({
3379
- type: Add,
3380
- nodes: treeToArray(newNode)
3381
- });
3309
+ return disabledClassName;
3382
3310
  };
3383
- const replaceTree = (newNode, patches) => {
3384
- patches.push({
3385
- type: Replace,
3386
- nodes: treeToArray(newNode)
3387
- });
3388
- };
3389
- const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3390
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3391
- if (nodePatches === null) {
3392
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3393
- replaceTree(newNode, patches);
3394
- return nextChildIndex;
3395
- }
3396
- const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3397
- if (nodePatches.length === 0 && !hasChildrenToCompare) {
3398
- return currentChildIndex;
3399
- }
3400
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3401
- if (nodePatches.length > 0) {
3402
- patches.push(...nodePatches);
3403
- }
3404
- if (hasChildrenToCompare) {
3405
- diffChildren(oldNode.children, newNode.children, patches);
3406
- }
3407
- return nextChildIndex;
3311
+ const getSearchFieldButtonVirtualDom = button => {
3312
+ const {
3313
+ enabled,
3314
+ icon,
3315
+ onClick,
3316
+ title
3317
+ } = button;
3318
+ return [{
3319
+ childCount: 1,
3320
+ className: getClassName(enabled),
3321
+ onClick,
3322
+ tabIndex: Focusable,
3323
+ title,
3324
+ type: Button$2
3325
+ }, {
3326
+ childCount: 0,
3327
+ className: mergeClassNames(MaskIcon, icon),
3328
+ type: Div
3329
+ }];
3408
3330
  };
3409
- const diffRootNode = (oldNode, newNode, patches) => {
3410
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3411
- if (nodePatches === null) {
3412
- replaceTree(newNode, patches);
3413
- return;
3414
- }
3415
- if (nodePatches.length > 0) {
3416
- patches.push(...nodePatches);
3417
- }
3418
- if (oldNode.children.length > 0 || newNode.children.length > 0) {
3419
- diffChildren(oldNode.children, newNode.children, patches);
3420
- }
3331
+
3332
+ const searchFieldNode = {
3333
+ childCount: 2,
3334
+ className: SearchField,
3335
+ role: None$1,
3336
+ type: Div
3421
3337
  };
3422
- const diffChildren = (oldChildren, newChildren, patches) => {
3423
- const maxLength = Math.max(oldChildren.length, newChildren.length);
3424
- let currentChildIndex = -1;
3425
- const indicesToRemove = [];
3426
- for (let i = 0; i < maxLength; i++) {
3427
- const oldNode = oldChildren[i];
3428
- const newNode = newChildren[i];
3429
- if (!oldNode && !newNode) {
3430
- continue;
3431
- }
3432
- if (!oldNode) {
3433
- currentChildIndex = navigateToParent(patches, currentChildIndex);
3434
- addTree(newNode, patches);
3435
- continue;
3436
- }
3437
- if (!newNode) {
3438
- indicesToRemove.push(i);
3439
- continue;
3440
- }
3441
- currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3442
- }
3443
- navigateToParent(patches, currentChildIndex);
3444
- for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3445
- patches.push({
3446
- type: RemoveChild,
3447
- index: indicesToRemove[j]
3338
+ const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
3339
+ // TODO avoid mutation
3340
+ const dom = [searchFieldNode, {
3341
+ autocapitalize: 'off',
3342
+ autocomplete: 'off',
3343
+ autocorrect: 'off',
3344
+ childCount: 0,
3345
+ className: MultilineInputBox,
3346
+ inputType: 'search',
3347
+ name,
3348
+ ...(onBlur && {
3349
+ onBlur
3350
+ }),
3351
+ onFocus,
3352
+ onInput,
3353
+ placeholder,
3354
+ spellcheck: false,
3355
+ type: Input$1,
3356
+ ...inputProperties
3357
+ }, {
3358
+ childCount: insideButtons.length,
3359
+ className: SearchFieldButtons,
3360
+ type: Div
3361
+ }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
3362
+ if (outsideButtons.length > 0) {
3363
+ dom.unshift({
3364
+ childCount: 1 + outsideButtons.length,
3365
+ className: SearchFieldContainer,
3366
+ role: None$1,
3367
+ type: Div
3448
3368
  });
3369
+ dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
3449
3370
  }
3450
- };
3451
- const diffTrees = (oldTree, newTree, patches, path) => {
3452
- if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3453
- diffRootNode(oldTree[0], newTree[0], patches);
3454
- return;
3455
- }
3456
- diffChildren(oldTree, newTree, patches);
3371
+ return dom;
3457
3372
  };
3458
3373
 
3459
- const removeTrailingNavigationPatches = patches => {
3460
- while (patches.length > 0) {
3461
- const patch = patches.at(-1);
3462
- if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3463
- break;
3464
- }
3465
- patches.pop();
3374
+ const Extensions = 'extensions';
3375
+
3376
+ const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
3377
+ if (!suggestOpen) {
3378
+ return [];
3466
3379
  }
3467
- return patches;
3380
+ return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
3468
3381
  };
3469
-
3470
- const diffTree = (oldNodes, newNodes) => {
3471
- // Step 1: Convert flat arrays to tree structures
3472
- const oldTree = arrayToTree(oldNodes);
3473
- const newTree = arrayToTree(newNodes);
3474
- // Step 3: Compare the trees
3475
- const patches = [];
3476
- diffTrees(oldTree, newTree, patches, []);
3477
- // Remove trailing navigation patches since they serve no purpose
3478
- return removeTrailingNavigationPatches(patches);
3382
+ const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
3383
+ const inputProperties = suggestOpen ? {
3384
+ ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
3385
+ ariaAutoComplete: 'list',
3386
+ ariaControls: 'ExtensionSearchCompletions',
3387
+ ariaExpanded: true,
3388
+ role: ComboBox
3389
+ } : {
3390
+ ariaAutoComplete: 'list',
3391
+ ariaExpanded: false,
3392
+ role: ComboBox
3393
+ };
3394
+ const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
3395
+ return [{
3396
+ childCount: suggestOpen ? 2 : 1,
3397
+ className: ExtensionHeader,
3398
+ onContextMenu: HandleHeaderContextMenu,
3399
+ type: Div
3400
+ }, ...getSearchFieldVirtualDom(Extensions, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
3479
3401
  };
3480
3402
 
3481
- const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
3482
- const relativeDeltaY = deltaY % itemHeight;
3483
- const relativeY = eventY - y - headerHeight + relativeDeltaY;
3484
- const index = Math.floor(relativeY / itemHeight);
3485
- return index;
3486
- };
3403
+ const Extension = 'Extension';
3487
3404
 
3488
- const handleCompletionPointerDown = (state, label) => {
3489
- if (state.completionItems.every(item => item.label !== label)) {
3490
- return Promise.resolve(state);
3491
- }
3492
- return acceptCompletion(state, label);
3405
+ const install = {
3406
+ disabled: false,
3407
+ label: install$1(),
3408
+ onClick: HandleInstall
3493
3409
  };
3494
-
3495
- const handleClickAt = async (state, button, eventX, eventY, name = '') => {
3496
- if (name.startsWith('@')) {
3497
- return handleCompletionPointerDown(state, name);
3410
+ const installing = {
3411
+ disabled: true,
3412
+ label: installing$1(),
3413
+ onClick: HandleInstall
3414
+ };
3415
+ const enable = {
3416
+ disabled: false,
3417
+ label: enable$2(),
3418
+ onClick: HandleEnable
3419
+ };
3420
+ const disable = {
3421
+ disabled: false,
3422
+ label: disable$2(),
3423
+ onClick: HandleDisable
3424
+ };
3425
+ const uninstall = {
3426
+ disabled: false,
3427
+ label: uninstall$1(),
3428
+ onClick: HandleUninstall
3429
+ };
3430
+ const uninstalling = {
3431
+ disabled: true,
3432
+ label: uninstalling$1(),
3433
+ onClick: HandleUninstall
3434
+ };
3435
+ const getExtensionActions = (builtin, disabled, status) => {
3436
+ if (status === NotInstalled) {
3437
+ return [install];
3498
3438
  }
3499
- if (name) {
3500
- return state;
3439
+ if (status === Installing) {
3440
+ return [installing];
3501
3441
  }
3502
- if (button !== LeftClick) {
3503
- return state;
3442
+ if (status === Uninstalling) {
3443
+ return builtin ? [] : [uninstalling];
3504
3444
  }
3505
- const {
3506
- deltaY,
3507
- headerHeight,
3508
- itemHeight,
3509
- x,
3510
- y
3511
- } = state;
3512
- const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3513
- return handleClick(state, index);
3514
- };
3515
-
3516
- const handleClickCurrent = state => {
3517
- const {
3518
- focusedIndex
3519
- } = state;
3520
- return handleClick(state, focusedIndex);
3445
+ const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
3446
+ return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
3521
3447
  };
3522
3448
 
3523
- const handleClickCurrentButKeepFocus = state => {
3524
- const {
3525
- focusedIndex
3526
- } = state;
3527
- return handleClick(state, focusedIndex);
3449
+ const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
3450
+ const getExtensionActionVirtualDom = (action, id) => {
3451
+ return [{
3452
+ childCount: 1,
3453
+ className,
3454
+ disabled: action.disabled,
3455
+ name: id,
3456
+ onClick: action.onClick,
3457
+ type: Button$2
3458
+ }, text(action.label)];
3528
3459
  };
3529
3460
 
3530
- const show2 = async (uid, menuId, x, y, args) => {
3531
- await showContextMenu2(uid, menuId, x, y, args);
3461
+ const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
3462
+ const actions = getExtensionActions(builtin, disabled, status);
3463
+ return [{
3464
+ childCount: actions.length,
3465
+ className: ExtensionActions,
3466
+ type: Div
3467
+ }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
3532
3468
  };
3533
3469
 
3534
- const handleClickFilter = async state => {
3535
- const {
3536
- headerHeight,
3537
- uid,
3538
- width,
3539
- x,
3540
- y
3541
- } = state;
3542
- const menuX = x + width + 60;
3543
- const menuHeight = 370;
3544
- const menuY = y + headerHeight + menuHeight;
3545
- await show2(uid, ExtensionSearchFilter, menuX, menuY, {
3546
- menuId: ExtensionSearchFilter
3547
- });
3548
- return state;
3470
+ const getExtensionListItemClassName = (focused, disabled) => {
3471
+ return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
3549
3472
  };
3550
3473
 
3551
- const handleContextMenu = async (state, button, eventX, eventY) => {
3552
- // TODO use focused index when when context menu button is -1 (keyboard)
3553
- const {
3554
- deltaY,
3555
- headerHeight,
3556
- itemHeight,
3557
- items,
3558
- minLineY,
3559
- uid,
3560
- x,
3561
- y
3562
- } = state;
3563
- const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3564
- const index = visibleIndex + minLineY;
3565
- if (index < 0 || index >= items.length) {
3566
- return state;
3567
- }
3568
- const item = items[index];
3569
- await show2(uid, ManageExtension, eventX, eventY, {
3570
- builtin: item.builtin === true,
3571
- disabled: item.disabled === true,
3572
- menuId: ManageExtension,
3573
- status: item.status
3574
- });
3474
+ const getExtensionListItemFooter = hasStatistics => {
3575
3475
  return {
3576
- ...state,
3577
- focusedIndex: index
3476
+ childCount: hasStatistics ? 3 : 2,
3477
+ className: ExtensionListItemFooter,
3478
+ type: Div
3578
3479
  };
3579
3480
  };
3580
3481
 
3581
- const handleDisableWorkspace = (state, id) => {
3582
- return state;
3482
+ const getExtensionListItemId = focused => {
3483
+ if (focused) {
3484
+ return `ExtensionActive`;
3485
+ }
3486
+ return undefined;
3583
3487
  };
3584
3488
 
3585
- const handleEnableWorkspace = (state, id) => {
3586
- return state;
3489
+ const extensionListItemMetadataNode = {
3490
+ childCount: 2,
3491
+ className: ExtensionListItemMetadata,
3492
+ type: Div
3493
+ };
3494
+ const getStatisticVirtualDom = (label, value, className) => {
3495
+ const accessibleLabel = `${label}: ${value}`;
3496
+ return [{
3497
+ ariaLabel: accessibleLabel,
3498
+ childCount: 1,
3499
+ className: mergeClassNames(ExtensionListItemStatistic, className),
3500
+ title: accessibleLabel,
3501
+ type: Span
3502
+ }, text(value)];
3503
+ };
3504
+ const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
3505
+ return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
3587
3506
  };
3588
3507
 
3589
- const Web = 1;
3590
- const Electron = 2;
3591
- const Remote = 3;
3592
-
3593
- const getAllExtensions$1 = async (assetDir, platform) => {
3594
- try {
3595
- return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
3596
- } catch (error) {
3597
- if (platform === Web) {
3598
- return [];
3599
- }
3600
- throw error;
3508
+ const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
3509
+ if (!hasStatistics) {
3510
+ return [];
3601
3511
  }
3512
+ return getExtensionStatisticsVirtualDom(downloadCount, rating);
3602
3513
  };
3603
3514
 
3604
- const getAllExtensions = (assetDir, platform) => {
3605
- return getAllExtensions$1(assetDir, platform);
3515
+ const getListItemDetail = linked => {
3516
+ return {
3517
+ childCount: linked ? 4 : 3,
3518
+ className: ExtensionListItemDetail,
3519
+ type: Div
3520
+ };
3606
3521
  };
3607
-
3608
- const getBuiltin = extension => {
3609
- if (extension === null || typeof extension !== 'object') {
3610
- return false;
3522
+ const listItemName = {
3523
+ childCount: 1,
3524
+ className: ExtensionListItemName,
3525
+ type: Div
3526
+ };
3527
+ const listItemDescription = {
3528
+ childCount: 1,
3529
+ className: ExtensionListItemDescription,
3530
+ type: Div
3531
+ };
3532
+ const listItemAuthorName = {
3533
+ childCount: 1,
3534
+ className: ExtensionListItemAuthorName,
3535
+ type: Div
3536
+ };
3537
+ const getLinkedIconVirtualDom = linked$1 => {
3538
+ if (!linked$1) {
3539
+ return [];
3611
3540
  }
3541
+ const label = linked();
3542
+ return [{
3543
+ ariaLabel: label,
3544
+ childCount: 0,
3545
+ className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
3546
+ role: Image,
3547
+ title: 'Extension is linked',
3548
+ type: Div
3549
+ }];
3550
+ };
3551
+ const getExtensionListItemVirtualDom = extension => {
3612
3552
  const {
3613
- builtin,
3553
+ builtin = false,
3554
+ description,
3555
+ disabled = false,
3556
+ downloadCount = 'n/a',
3557
+ focused,
3558
+ icon,
3614
3559
  id,
3615
- isBuiltin
3560
+ linked = false,
3561
+ name,
3562
+ posInSet,
3563
+ publisher,
3564
+ rating = 'n/a',
3565
+ setSize,
3566
+ status
3616
3567
  } = extension;
3617
- return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
3568
+ const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
3569
+ const hasStatistics = !builtin;
3570
+ const dom = [{
3571
+ ariaPosInSet: posInSet,
3572
+ ariaRoleDescription: Extension,
3573
+ ariaSetSize: setSize,
3574
+ childCount: 2,
3575
+ className: getExtensionListItemClassName(focused, disabled),
3576
+ id: getExtensionListItemId(focused),
3577
+ role: ListItem,
3578
+ type: Div
3579
+ }, {
3580
+ childCount: 0,
3581
+ className: ExtensionListItemIcon,
3582
+ role: None$1,
3583
+ src: icon,
3584
+ type: Img
3585
+ }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
3586
+ return dom;
3618
3587
  };
3619
3588
 
3620
- const isString = item => {
3621
- return typeof item === 'string';
3589
+ const getListClassName = focusOutline => {
3590
+ const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
3591
+ return className;
3622
3592
  };
3623
-
3624
- const getCategories = extension => {
3625
- if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
3626
- return [];
3627
- }
3628
- return extension.categories.filter(isString);
3593
+ const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
3594
+ const dom = [{
3595
+ ariaLabel: extensions(),
3596
+ childCount: visibleExtensions.length,
3597
+ className: getListClassName(focusOutline),
3598
+ onBlur: HandleBlur,
3599
+ onContextmenu: HandleContextMenu,
3600
+ onContextMenu: HandleContextMenu,
3601
+ onFocus: HandleFocus,
3602
+ onPointerDown: HandlePointerDown,
3603
+ onTouchEnd: HandleTouchEnd,
3604
+ onTouchMove: HandleTouchMove,
3605
+ onTouchStart: HandleTouchStart,
3606
+ onWheel: HandleWheel,
3607
+ role: List,
3608
+ tabIndex: Focusable,
3609
+ type: Div
3610
+ }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
3611
+ return dom;
3629
3612
  };
3630
3613
 
3631
- const getDescription = extension => {
3632
- if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
3633
- return 'n/a';
3634
- }
3635
- return extension.description;
3614
+ const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
3615
+ const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
3616
+ // TODO
3617
+ return dom;
3636
3618
  };
3637
3619
 
3638
- const getDisabled = extension => {
3639
- return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
3620
+ const noExtensionsFoundNode = {
3621
+ childCount: 1,
3622
+ className: NoExtensionsFoundMessage,
3623
+ type: Div
3624
+ };
3625
+ const getNoExtensionsFoundVirtualDom = message => {
3626
+ return [noExtensionsFoundNode, text(message)];
3640
3627
  };
3641
3628
 
3642
- const getDownloadCountValue = extension => {
3643
- if (extension === null || typeof extension !== 'object') {
3644
- return undefined;
3645
- }
3646
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3647
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3648
- return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
3629
+ const scrollBarNode = {
3630
+ childCount: 1,
3631
+ className: mergeClassNames(ScrollBar, ScrollBarSmall),
3632
+ onPointerDown: HandleScrollBarPointerDown,
3633
+ // TODO support pointercapture event
3634
+ type: Div
3649
3635
  };
3650
- const getDownloadCount = extension => {
3651
- const downloadCount = getDownloadCountValue(extension);
3652
- if (typeof downloadCount !== 'number') {
3653
- return 'n/a';
3636
+ const scrollBarThumbNode = {
3637
+ childCount: 0,
3638
+ className: ScrollBarThumb,
3639
+ type: Div
3640
+ };
3641
+ const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
3642
+ const shouldShowScrollbar = scrollBarHeight > 0;
3643
+ if (!shouldShowScrollbar) {
3644
+ return [];
3654
3645
  }
3655
- return downloadCount.toLocaleString();
3646
+ return [scrollBarNode, scrollBarThumbNode];
3656
3647
  };
3657
3648
 
3658
- const getRemoteUrl = (extension, platform, assetDir) => {
3659
- if (extension === null || typeof extension !== 'object') {
3660
- return '';
3661
- }
3662
- const builtin = 'builtin' in extension && extension.builtin === true;
3663
- const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
3664
- const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
3665
- const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
3666
- if (platform === Remote || platform === Electron) {
3667
- if (builtin) {
3668
- return `${assetDir}/extensions/${id}/${icon}`;
3669
- }
3670
- return `/remote/${path}/${icon}`; // TODO support windows paths
3671
- }
3672
- if (platform === Web) {
3673
- return `${path}/${icon}`;
3674
- }
3675
- return '';
3676
- };
3677
-
3678
- const getExtensionDefaultIcon = assetDir => {
3679
- return `${assetDir}/icons/extensionDefaultIcon.png`;
3680
- };
3681
- const getExtensionLanguageBasicsIcon = assetDir => {
3682
- return `${assetDir}/icons/language-icon.svg`;
3683
- };
3684
- const getExtensionThemeIcon = assetDir => {
3685
- return `${assetDir}/icons/theme-icon.png`;
3686
- };
3687
-
3688
- const isLanguageBasicsExtension = extension => {
3689
- return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
3690
- };
3691
- const isThemeExtension = extension => {
3692
- return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
3693
- };
3694
- const getExtensionIcon = (extension, platform, assetDir) => {
3695
- if (extension === null || typeof extension !== 'object') {
3696
- return getExtensionDefaultIcon(assetDir);
3697
- }
3698
- const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
3699
- const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
3700
- if (!hasPath || !hasIcon) {
3701
- if (isLanguageBasicsExtension(extension)) {
3702
- return getExtensionLanguageBasicsIcon(assetDir);
3703
- }
3704
- if (isThemeExtension(extension)) {
3705
- return getExtensionThemeIcon(assetDir);
3706
- }
3707
- return getExtensionDefaultIcon(assetDir);
3708
- }
3709
- return getRemoteUrl(extension, platform, assetDir);
3710
- };
3711
-
3712
- const getIcon = (extension, platform, assetDir) => {
3713
- return getExtensionIcon(extension, platform, assetDir);
3714
- };
3715
-
3716
- const getId = extension => {
3717
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
3718
- return 'n/a';
3719
- }
3720
- return extension.id;
3721
- };
3722
-
3723
- const getLinked = extension => {
3724
- if (extension === null || typeof extension !== 'object') {
3725
- return false;
3726
- }
3727
- if ('linked' in extension && extension.linked === true) {
3728
- return true;
3729
- }
3730
- return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
3731
- };
3732
-
3733
- const getName = extension => {
3734
- if (extension === null || typeof extension !== 'object') {
3735
- return 'n/a';
3736
- }
3737
- if ('name' in extension && typeof extension.name === 'string' && extension.name) {
3738
- return extension.name;
3739
- }
3740
- if ('id' in extension && typeof extension.id === 'string' && extension.id) {
3741
- return extension.id;
3742
- }
3743
- return 'n/a';
3649
+ const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
3650
+ // TODO use normal parameters
3651
+ const {
3652
+ builtin,
3653
+ description,
3654
+ disabled,
3655
+ downloadCount,
3656
+ icon,
3657
+ id,
3658
+ linked,
3659
+ name,
3660
+ publisher,
3661
+ rating,
3662
+ status
3663
+ } = item;
3664
+ return {
3665
+ builtin,
3666
+ description,
3667
+ disabled,
3668
+ downloadCount,
3669
+ focused: i === focusedIndex,
3670
+ icon,
3671
+ id,
3672
+ index: i,
3673
+ linked,
3674
+ name,
3675
+ posInSet: i + 1,
3676
+ publisher,
3677
+ rating,
3678
+ setSize,
3679
+ status,
3680
+ top: (i - minLineY) * itemHeight - relative
3681
+ };
3744
3682
  };
3745
3683
 
3746
- const RE_PUBLISHER = /^[a-z\d-]+/;
3747
- const getPublisher = extension => {
3748
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
3749
- return 'n/a';
3750
- }
3751
- const match = extension.id.match(RE_PUBLISHER);
3752
- if (!match) {
3753
- return 'n/a';
3684
+ const getVisible = state => {
3685
+ const {
3686
+ deltaY,
3687
+ focusedIndex,
3688
+ itemHeight,
3689
+ items,
3690
+ maxLineY,
3691
+ minLineY
3692
+ } = state;
3693
+ const setSize = items.length;
3694
+ const visible = [];
3695
+ const relative = deltaY % itemHeight;
3696
+ for (let i = minLineY; i < maxLineY; i++) {
3697
+ const item = items[i];
3698
+ visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
3754
3699
  }
3755
- return match[0];
3700
+ return visible;
3756
3701
  };
3757
3702
 
3758
- const getRatingValue = extension => {
3759
- if (extension === null || typeof extension !== 'object') {
3760
- return undefined;
3761
- }
3762
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3763
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3764
- return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
3703
+ const contentNode = {
3704
+ childCount: 2,
3705
+ className: mergeClassNames(Viewlet, List$1),
3706
+ type: Div
3765
3707
  };
3766
- const getRating = extension => {
3767
- const rating = getRatingValue(extension);
3768
- if (typeof rating !== 'number') {
3769
- return 'n/a';
3770
- }
3771
- return rating.toFixed(1);
3708
+ const extensionsNode = {
3709
+ ariaBusy: false,
3710
+ ariaLive: 'polite',
3711
+ childCount: 2,
3712
+ className: mergeClassNames(Viewlet, Extensions$1),
3713
+ role: None$3,
3714
+ type: Div
3772
3715
  };
3773
-
3774
- const getSize = extension => {
3775
- if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
3776
- return 0;
3716
+ const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
3717
+ if (message) {
3718
+ return getNoExtensionsFoundVirtualDom(message);
3777
3719
  }
3778
- return extension.size;
3720
+ return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
3779
3721
  };
3780
-
3781
- const getStatus = extension => {
3782
- if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
3783
- return undefined;
3784
- }
3785
- return typeof extension.status === 'string' ? extension.status : undefined;
3722
+ const getExtensionsViewVirtualDom = state => {
3723
+ const visibleExtensions = getVisible(state);
3724
+ const {
3725
+ completionFocusedIndex,
3726
+ completionItems,
3727
+ focus,
3728
+ focusedIndex,
3729
+ inputActions,
3730
+ message,
3731
+ placeholder,
3732
+ scrollBarHeight,
3733
+ scrollBarY,
3734
+ suggestOpen
3735
+ } = state;
3736
+ const focusOutline = focusedIndex === -1 && focus === List$2;
3737
+ return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
3786
3738
  };
3787
3739
 
3788
- const getUpdatedDate = extension => {
3789
- if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
3790
- return 0;
3740
+ const renderItems2 = newState => {
3741
+ const {
3742
+ initial,
3743
+ uid
3744
+ } = newState;
3745
+ if (initial) {
3746
+ return [SetDom2, uid, []];
3791
3747
  }
3792
- return extension.updatedDate;
3793
- };
3794
-
3795
- const normalizeExtension = (extension, platform, assetDir) => {
3796
- return {
3797
- builtin: getBuiltin(extension),
3798
- categories: getCategories(extension),
3799
- description: getDescription(extension),
3800
- disabled: getDisabled(extension),
3801
- downloadCount: getDownloadCount(extension),
3802
- icon: getIcon(extension, platform, assetDir),
3803
- id: getId(extension),
3804
- linked: getLinked(extension),
3805
- name: getName(extension),
3806
- publisher: getPublisher(extension),
3807
- rating: getRating(extension),
3808
- size: getSize(extension),
3809
- status: getStatus(extension),
3810
- updatedDate: getUpdatedDate(extension),
3811
- uri: ''
3812
- };
3748
+ const dom = getExtensionsViewVirtualDom(newState);
3749
+ return [SetDom2, uid, dom];
3813
3750
  };
3814
3751
 
3815
- const normalizeExtensions = (extensions, platform, assetDir) => {
3816
- return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
3752
+ const getComponentDom = uid => {
3753
+ const state = getComponentState(uid);
3754
+ return renderItems2(state)[2];
3817
3755
  };
3818
3756
 
3819
- const handleExtensionsChanged = async state => {
3820
- const {
3821
- assetDir,
3822
- platform
3823
- } = state;
3824
- const allExtensions = await getAllExtensions(assetDir, platform);
3825
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
3826
- return handleChange({
3827
- ...state,
3828
- allExtensions: normalized
3829
- }, {});
3830
- };
3757
+ const Tab = 2;
3758
+ const Enter = 3;
3759
+ const Escape = 8;
3760
+ const Space = 9;
3761
+ const PageUp = 10;
3762
+ const PageDown = 11;
3763
+ const End = 255;
3764
+ const Home = 12;
3765
+ const UpArrow = 14;
3766
+ const DownArrow = 16;
3831
3767
 
3832
- const handleFocus = async state => {
3833
- return {
3834
- ...state,
3835
- focus: List$2
3836
- };
3837
- };
3768
+ const CtrlCmd = 1 << 11 >>> 0;
3838
3769
 
3839
- const handleHeaderContextMenu = async state => {
3840
- return state;
3841
- };
3770
+ const FocusExtensions = 15;
3771
+ const FocusExtensionsInput = 7000;
3842
3772
 
3843
- const handleInputFocus = state => {
3844
- return {
3845
- ...state,
3846
- focus: Input
3847
- };
3848
- };
3849
-
3850
- const handleInstall = async (state, id) => {
3851
- await installExtension(id);
3852
- return setExtensionStatus(state, id, Enabled);
3853
- };
3854
-
3855
- const state = {
3856
- connected: false
3857
- };
3858
- const isConnected = () => {
3859
- const {
3860
- connected
3861
- } = state;
3862
- return connected;
3863
- };
3864
- const invoke = (method, ...params) => {
3865
- return invoke$2(method, ...params);
3866
- };
3867
- const set = rpc => {
3868
- set$3(rpc);
3869
- state.connected = true;
3870
- };
3871
-
3872
- const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
3873
- const executeViewletCommand = async (uid, command, ...args) => {
3874
- const fn = viewletCommandMap[`SearchExtensions.${command}`];
3875
- if (typeof fn !== 'function') {
3876
- throw new TypeError(`Viewlet command not found: ${command}`);
3877
- }
3878
- await fn(uid, ...args);
3879
- await invoke$1('Viewlet.requestRender', uid);
3880
- };
3881
- const rpc = await create$7({
3882
- commandMap: {
3883
- 'Viewlet.executeViewletCommand': executeViewletCommand
3884
- },
3885
- messagePort: port
3886
- });
3887
- if (setAsRendererProcess) {
3888
- set(rpc);
3889
- }
3890
- };
3891
-
3892
- const handleScrollBarCaptureLost = state => {
3893
- return {
3894
- ...state,
3895
- scrollBarActive: false
3896
- };
3897
- };
3898
-
3899
- const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
3900
- const halfScrollBarHeight = scrollBarHeight / 2;
3901
- if (relativeY <= halfScrollBarHeight) {
3902
- // clicked at top
3903
- return {
3904
- handleOffset: relativeY,
3905
- percent: 0
3906
- };
3907
- }
3908
- if (relativeY <= height - halfScrollBarHeight) {
3909
- // clicked in middle
3910
- return {
3911
- handleOffset: halfScrollBarHeight,
3912
- percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
3913
- };
3914
- }
3915
- // clicked at bottom
3916
- return {
3917
- handleOffset: scrollBarHeight - height + relativeY,
3918
- percent: 1
3919
- };
3920
- };
3921
-
3922
- const clamp = (num, min, max) => {
3923
- number(num);
3924
- number(min);
3925
- number(max);
3926
- return Math.min(Math.max(num, min), max);
3773
+ const getKeyBindings = () => {
3774
+ return [{
3775
+ command: 'Extensions.closeSuggest',
3776
+ key: Escape,
3777
+ when: FocusExtensionsInput
3778
+ }, {
3779
+ command: 'Extensions.acceptCompletion',
3780
+ key: Enter,
3781
+ when: FocusExtensionsInput
3782
+ }, {
3783
+ command: 'Extensions.acceptCompletion',
3784
+ key: Tab,
3785
+ when: FocusExtensionsInput
3786
+ }, {
3787
+ command: 'Extensions.selectPreviousCompletion',
3788
+ key: UpArrow,
3789
+ when: FocusExtensionsInput
3790
+ }, {
3791
+ command: 'Extensions.selectNextCompletion',
3792
+ key: DownArrow,
3793
+ when: FocusExtensionsInput
3794
+ }, {
3795
+ command: 'Extensions.openSuggest',
3796
+ key: CtrlCmd | Space,
3797
+ when: FocusExtensionsInput
3798
+ }, {
3799
+ command: 'Extensions.focusFirst',
3800
+ key: Home,
3801
+ when: FocusExtensions
3802
+ }, {
3803
+ command: 'Extensions.focusLast',
3804
+ key: End,
3805
+ when: FocusExtensions
3806
+ }, {
3807
+ command: 'Extensions.focusPreviousPage',
3808
+ key: PageUp,
3809
+ when: FocusExtensions
3810
+ }, {
3811
+ command: 'Extensions.focusNextPage',
3812
+ key: PageDown,
3813
+ when: FocusExtensions
3814
+ }, {
3815
+ command: 'Extensions.focusPrevious',
3816
+ key: UpArrow,
3817
+ when: FocusExtensions
3818
+ }, {
3819
+ command: 'Extensions.focusNext',
3820
+ key: DownArrow,
3821
+ when: FocusExtensions
3822
+ }, {
3823
+ command: 'Extensions.handleClickCurrentButKeepFocus',
3824
+ key: Space,
3825
+ when: FocusExtensions
3826
+ }, {
3827
+ command: 'Extensions.handleClickCurrent',
3828
+ key: Enter,
3829
+ when: FocusExtensions
3830
+ }, {
3831
+ command: 'Extensions.toggleSuggest',
3832
+ key: CtrlCmd | Space,
3833
+ when: FocusExtensions
3834
+ }, {
3835
+ command: 'Extensions.scrollDown',
3836
+ key: CtrlCmd | DownArrow,
3837
+ when: FocusExtensions
3838
+ }];
3927
3839
  };
3928
3840
 
3929
- const setDeltaY = (state, value) => {
3930
- object(state);
3931
- number(value);
3932
- const {
3933
- deltaY,
3934
- finalDeltaY,
3935
- headerHeight,
3936
- height,
3937
- itemHeight,
3938
- items,
3939
- minimumSliderSize
3940
- } = state;
3941
- const listHeight = height - headerHeight;
3942
- const newDeltaY = clamp(value, 0, finalDeltaY);
3943
- if (deltaY === newDeltaY) {
3944
- return state;
3945
- }
3946
- // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
3947
- const minLineY = Math.floor(newDeltaY / itemHeight);
3948
- const total = items.length;
3949
- const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
3950
- const contentHeight = total * itemHeight;
3951
- const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
3952
- const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
3953
- return {
3954
- ...state,
3955
- deltaY: newDeltaY,
3956
- maxLineY,
3957
- minLineY,
3958
- scrollBarY
3959
- };
3960
- };
3841
+ const Separator = 1;
3842
+ const None = 0;
3843
+ const SubMenu = 4;
3844
+ const Disabled = 5;
3961
3845
 
3962
- const handleScrollBarClick = (state, eventY) => {
3963
- // TODO move this to list
3964
- const {
3965
- deltaY,
3966
- finalDeltaY,
3967
- headerHeight,
3968
- height,
3969
- scrollBarHeight,
3970
- y
3971
- } = state;
3972
- const contentHeight = height - headerHeight;
3973
- const relativeY = eventY - y - headerHeight;
3974
- const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
3975
- const diff = relativeY - currentScrollBarY;
3976
- if (diff >= 0 && diff < scrollBarHeight) {
3846
+ const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
3847
+ const getEnablementFlags = (disabled, status) => {
3848
+ if (status && nonEnableableStatuses.includes(status)) {
3977
3849
  return {
3978
- ...state,
3979
- handleOffset: diff,
3980
- scrollBarActive: true
3850
+ disable: Disabled,
3851
+ enable: Disabled
3981
3852
  };
3982
3853
  }
3983
- const {
3984
- handleOffset,
3985
- percent
3986
- } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
3987
- const newDeltaY = percent * finalDeltaY;
3854
+ const isDisabled = status === Disabled$1 || status === undefined && disabled;
3988
3855
  return {
3989
- ...setDeltaY(state, newDeltaY),
3990
- handleOffset,
3991
- scrollBarActive: true
3856
+ disable: isDisabled ? Disabled : None,
3857
+ enable: isDisabled ? None : Disabled
3992
3858
  };
3993
3859
  };
3994
-
3995
- const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
3996
- if (relativeY <= contentHeight - scrollBarHeight / 2) {
3997
- // clicked in middle
3998
- return relativeY / (contentHeight - scrollBarHeight);
3999
- }
4000
- // clicked at bottom
4001
- return 1;
4002
- };
4003
- const handleScrollBarMove = (state, eventY) => {
4004
- const {
4005
- finalDeltaY,
4006
- handleOffset,
4007
- headerHeight,
4008
- height,
4009
- scrollBarActive,
4010
- scrollBarHeight,
4011
- y
4012
- } = state;
4013
- if (!scrollBarActive) {
4014
- return state;
4015
- }
4016
- const relativeY = eventY - y - headerHeight - handleOffset;
4017
- const contentHeight = height - headerHeight;
4018
- const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4019
- const newDeltaY = newPercent * finalDeltaY;
4020
- return setDeltaY(state, newDeltaY);
4021
- };
4022
-
4023
- const handleSettingsButtonClick = async (state, index) => {
4024
- const {
4025
- deltaY,
4026
- headerHeight,
4027
- itemHeight,
4028
- items,
4029
- uid,
4030
- x,
4031
- y
4032
- } = state;
4033
- const actualIndex = index;
4034
- if (actualIndex < 0 || actualIndex >= items.length) {
4035
- return state;
4036
- }
4037
-
4038
- // Calculate the position for the context menu
4039
- // The settings button is at the bottom right of the extension list item
4040
- const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4041
- const menuX = x + 200; // Position near the right side of the extension item
4042
- const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4043
-
4044
- await show2(uid, ManageExtension, menuX, menuY, {
4045
- builtin: items[actualIndex].builtin === true,
4046
- disabled: items[actualIndex].disabled === true,
4047
- menuId: ManageExtension,
4048
- status: items[actualIndex].status
4049
- });
4050
- return state;
4051
- };
4052
-
4053
- const handleUninstall = async (state, id) => {
4054
- try {
4055
- await uninstall$2(id);
4056
- return setExtensionStatus(state, id, NotInstalled);
4057
- } catch (error) {
4058
- await showErrorDialog(error);
4059
- return state;
4060
- }
4061
- };
4062
-
4063
- const handleWheel = (state, deltaMode, deltaY) => {
4064
- number(deltaMode);
4065
- number(deltaY);
4066
- return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
3860
+ const getMenuEntriesList = (builtin, disabled = false, status) => {
3861
+ const enablementFlags = getEnablementFlags(disabled, status);
3862
+ return [{
3863
+ command: 'Extensions.enable',
3864
+ flags: enablementFlags.enable,
3865
+ id: 'enable',
3866
+ label: enable$2()
3867
+ }, {
3868
+ command: 'Extensions.enableWorkspace',
3869
+ flags: enablementFlags.enable,
3870
+ id: 'enableWorkspace',
3871
+ label: enableWorkspace$1()
3872
+ }, {
3873
+ command: '',
3874
+ flags: Separator,
3875
+ id: 'separator1',
3876
+ label: ''
3877
+ }, {
3878
+ command: 'Extensions.disable',
3879
+ flags: enablementFlags.disable,
3880
+ id: 'disable',
3881
+ label: disable$2()
3882
+ }, {
3883
+ command: 'Extensions.disableWorkspace',
3884
+ flags: enablementFlags.disable,
3885
+ id: 'disableWorkspace',
3886
+ label: disableWorkspace$1()
3887
+ }, {
3888
+ command: '',
3889
+ flags: Separator,
3890
+ id: 'separator2',
3891
+ label: ''
3892
+ }, {
3893
+ command: 'Extensions.installAnotherVersion',
3894
+ flags: Disabled,
3895
+ id: 'installAnotherVersion',
3896
+ label: installAnotherVersion$1()
3897
+ }, {
3898
+ command: 'Extensions.copyExtensionInfo',
3899
+ flags: None,
3900
+ id: 'copy',
3901
+ label: copy()
3902
+ }, {
3903
+ command: 'Extensions.copyExtensionId',
3904
+ flags: None,
3905
+ id: 'copyExtensionId',
3906
+ label: copyExtensionId$1()
3907
+ }];
4067
3908
  };
4068
3909
 
4069
- const createExtensionManagementWorkerRpc = async () => {
4070
- try {
4071
- const rpc = await create$6({
4072
- commandMap: {},
4073
- send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4074
- });
4075
- return rpc;
4076
- } catch (error) {
4077
- throw new VError(error, `Failed to create extension management rpc`);
4078
- }
3910
+ const getMenuEntriesFilter = () => {
3911
+ return [{
3912
+ command: 'Extensions.filterByFeatured',
3913
+ flags: None,
3914
+ id: 'filterByFeatured',
3915
+ label: featured()
3916
+ }, {
3917
+ command: 'Extensions.filterByMcpServers',
3918
+ flags: None,
3919
+ id: 'filterByMcpServers',
3920
+ label: mcpServers()
3921
+ }, {
3922
+ command: 'Extensions.filterByMostPopular',
3923
+ flags: None,
3924
+ id: 'filterByMostPopular',
3925
+ label: mostPopular()
3926
+ }, {
3927
+ command: 'Extensions.filterByRecentlyPublished',
3928
+ flags: None,
3929
+ id: 'filterByRecentlyPublished',
3930
+ label: recentlyPublished()
3931
+ }, {
3932
+ command: 'Extensions.filterByRecommended',
3933
+ flags: None,
3934
+ id: 'filterByRecommended',
3935
+ label: recommended()
3936
+ }, {
3937
+ command: '',
3938
+ flags: Separator,
3939
+ id: 'separator1',
3940
+ label: ''
3941
+ }, {
3942
+ command: 'SearchExtensions.filterByCategory',
3943
+ flags: SubMenu,
3944
+ id: 'filterByCategory',
3945
+ label: category()
3946
+ }, {
3947
+ command: 'Extensions.filterByInstalled',
3948
+ flags: None,
3949
+ id: 'filterByInstalled',
3950
+ label: installed()
3951
+ }, {
3952
+ command: 'Extensions.filterByUpdates',
3953
+ flags: None,
3954
+ id: 'filterByUpdates',
3955
+ label: updates()
3956
+ }, {
3957
+ command: 'Extensions.filterByBuiltin',
3958
+ flags: None,
3959
+ id: 'filterByBuiltin',
3960
+ label: builtIn()
3961
+ }, {
3962
+ command: 'Extensions.filterByLinked',
3963
+ flags: None,
3964
+ id: 'filterByLinked',
3965
+ label: linked()
3966
+ }, {
3967
+ command: 'Extensions.filterByEnabled',
3968
+ flags: None,
3969
+ id: 'filterByEnabled',
3970
+ label: enabled()
3971
+ }, {
3972
+ command: 'Extensions.filterByDisabled',
3973
+ flags: None,
3974
+ id: 'filterByDisabled',
3975
+ label: disabled()
3976
+ }, {
3977
+ command: 'Extensions.filterByWorkspaceUnsupported',
3978
+ flags: None,
3979
+ id: 'filterByWorkspaceUnsupported',
3980
+ label: workspaceUnsupported()
3981
+ }, {
3982
+ command: '',
3983
+ flags: Separator,
3984
+ id: 'separator2',
3985
+ label: ''
3986
+ }, {
3987
+ command: 'SearchExtensions.filterBySortBy',
3988
+ flags: SubMenu,
3989
+ id: 'filterBySortBy',
3990
+ label: sortBy()
3991
+ }];
4079
3992
  };
4080
3993
 
4081
- const initializeExtensionManagementWorker = async () => {
4082
- try {
4083
- const rpc = await createExtensionManagementWorkerRpc();
4084
- set$4(rpc);
4085
- } catch {
4086
- // ignore
3994
+ const getMenuEntries2 = (state, props) => {
3995
+ const {
3996
+ menuId
3997
+ } = props;
3998
+ switch (menuId) {
3999
+ case ExtensionSearchFilter:
4000
+ return getMenuEntriesFilter();
4001
+ default:
4002
+ return getMenuEntriesList(props.builtin, props.disabled, props.status);
4087
4003
  }
4088
4004
  };
4089
4005
 
4090
- const initialize = async () => {
4091
- await initializeExtensionManagementWorker();
4092
- };
4093
-
4094
- const installAnotherVersion = async state => {
4095
- await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4096
- return state;
4006
+ const getMenuIds = () => {
4007
+ return [ManageExtension, ExtensionSearchFilter];
4097
4008
  };
4098
4009
 
4099
- const Small = 1;
4100
- const Normal = 2;
4101
- const Large = 3;
4102
-
4103
- const getViewletSize = width => {
4104
- if (width < 180) {
4105
- return Small;
4106
- }
4107
- if (width < 768) {
4108
- return Normal;
4109
- }
4110
- return Large;
4010
+ const handleBlur = state => {
4011
+ return {
4012
+ ...state,
4013
+ focus: None$2,
4014
+ suggestOpen: false
4015
+ };
4111
4016
  };
4112
4017
 
4113
- const getIsFirefox = () => {
4114
- const globalWithNavigator = globalThis;
4115
- return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4018
+ const getExtensionDetailUri = extensionId => {
4019
+ return `extension-detail://${extensionId}`;
4116
4020
  };
4117
4021
 
4118
- const getSavedValue = savedState => {
4119
- if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4120
- return savedState.searchValue;
4121
- }
4122
- return '';
4123
- };
4124
- const getSavedDeltaY = savedState => {
4125
- if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4126
- return savedState.deltaY;
4127
- }
4128
- return 0;
4022
+ const openUri = async uri => {
4023
+ return openUri$1(uri);
4129
4024
  };
4130
4025
 
4131
- const restoreState = savedState => {
4132
- const searchValue = getSavedValue(savedState);
4133
- const savedDeltaY = getSavedDeltaY(savedState);
4026
+ const selectIndex = (state, index) => {
4134
4027
  return {
4135
- deltaY: savedDeltaY,
4136
- searchValue
4028
+ ...state,
4029
+ focusedIndex: index
4137
4030
  };
4138
4031
  };
4139
4032
 
4140
- const loadContentWithContext = async (context, savedState) => {
4033
+ const handleClick = async (state, index) => {
4141
4034
  const {
4142
- uid
4143
- } = context.getState();
4144
- const loadToken = getToken(uid);
4145
- try {
4146
- const initialState = context.getState();
4147
- const {
4148
- assetDir,
4149
- platform,
4150
- width
4151
- } = initialState;
4152
- const {
4153
- deltaY,
4154
- searchValue: restoredSearchValue
4155
- } = restoreState(savedState);
4156
- const size = getViewletSize(width);
4157
- const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4158
- await context.updateState(state => {
4159
- if (!state.initial) {
4160
- return state;
4161
- }
4162
- return {
4163
- ...state,
4164
- deltaY,
4165
- initial: false,
4166
- inputSource: Script,
4167
- scrollSensitivity,
4168
- searchValue: restoredSearchValue,
4169
- size
4170
- };
4171
- });
4172
- const allExtensions = await getAllExtensions(assetDir, platform);
4173
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4174
- await context.updateState(state => ({
4035
+ items,
4036
+ minLineY
4037
+ } = state;
4038
+ const actualIndex = index + minLineY;
4039
+ if (actualIndex < 0 || actualIndex >= items.length) {
4040
+ return {
4175
4041
  ...state,
4176
- allExtensions: normalized
4177
- }));
4178
- await handleChangeWithContext(context, {}, false);
4179
- } finally {
4180
- finish(uid, loadToken);
4042
+ focus: List$2,
4043
+ focusedIndex: -1
4044
+ };
4181
4045
  }
4046
+ const extension = items[actualIndex];
4047
+ const uri = getExtensionDetailUri(extension.id);
4048
+ await openUri(uri);
4049
+ const partialNewState = selectIndex(state, actualIndex);
4050
+ const newState = {
4051
+ ...partialNewState,
4052
+ focus: List$2
4053
+ };
4054
+ return newState;
4182
4055
  };
4183
4056
 
4184
- const openSuggest = state => {
4185
- const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4186
- if (completionItems.length === 0) {
4187
- return state;
4057
+ const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
4058
+ const relativeDeltaY = deltaY % itemHeight;
4059
+ const relativeY = eventY - y - headerHeight + relativeDeltaY;
4060
+ const index = Math.floor(relativeY / itemHeight);
4061
+ return index;
4062
+ };
4063
+
4064
+ const handleCompletionPointerDown = (state, label) => {
4065
+ if (state.completionItems.every(item => item.label !== label)) {
4066
+ return Promise.resolve(state);
4188
4067
  }
4189
- return {
4190
- ...state,
4191
- completionFocusedIndex: 0,
4192
- completionItems,
4193
- suggestOpen: true
4194
- };
4068
+ return acceptCompletion(state, label);
4195
4069
  };
4196
4070
 
4197
- const getCss = state => {
4071
+ const handleClickAt = async (state, button, eventX, eventY, name = '') => {
4072
+ if (name.startsWith('@')) {
4073
+ return handleCompletionPointerDown(state, name);
4074
+ }
4075
+ if (name) {
4076
+ return state;
4077
+ }
4078
+ if (button !== LeftClick) {
4079
+ return state;
4080
+ }
4198
4081
  const {
4199
- cursorOffset,
4200
- deltaY,
4201
- headerHeight,
4202
- itemHeight,
4203
- scrollBarHeight,
4204
- scrollBarY,
4205
- width
4206
- } = state;
4207
- const relative = -(deltaY % itemHeight);
4208
- const roundedScrollBarY = Math.round(scrollBarY);
4209
- const maximumCompletionLeft = Math.max(8, width - 168);
4210
- const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4211
- const completionTop = Math.max(0, headerHeight - 10);
4212
- return `.Extensions .ScrollBarThumb {
4213
- height: ${scrollBarHeight}px;
4214
- translate: 0 ${roundedScrollBarY}px;
4215
- }
4216
-
4217
-
4218
- /* TODO: avoid using negative margin. find a better way*/
4219
- .ExtensionListItem:nth-child(1) {
4220
- margin-top: ${relative}px;
4221
- }
4222
-
4223
- .ExtensionListItem {
4224
- box-sizing: border-box;
4225
- position: relative !important;
4226
- flex-shrink: 0;
4227
- }
4228
-
4229
- .ExtensionListItemLinkedIcon {
4230
- position: absolute;
4231
- top: 6px;
4232
- right: 6px;
4233
- width: 14px;
4234
- height: 14px;
4235
- color: var(--WorkbenchForeground, rgb(188, 190, 190));
4236
- opacity: 0.8;
4237
- }
4238
-
4239
- .ExtensionListItemDisabled:not(.ExtensionActive) {
4240
- background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4241
- color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4242
- }
4243
-
4244
- .Extensions .ListItems {
4245
- display: flex;
4246
- flex-direction: column;
4247
- gap: 0;
4248
- overflow-y: hidden;
4249
- }
4250
-
4251
- .ExtensionHeader {
4252
- contain: layout style;
4253
- position: relative;
4254
- z-index: 1;
4255
- }
4256
-
4257
- .ExtensionListItemFooter {
4258
- justify-content: flex-end;
4259
- padding-right: 2px;
4260
- }
4261
-
4262
- .ExtensionListItemAuthorName {
4263
- flex: 1;
4264
- }
4265
-
4266
- .ExtensionActions {
4267
- display: flex;
4268
- gap: 6px;
4269
- }
4270
-
4271
- .ExtensionActionButton {
4272
- padding: 0 5px;
4273
- }
4274
-
4275
- .ExtensionSearchCompletionWidget {
4276
- position: absolute;
4277
- left: ${completionLeft}px;
4278
- top: ${completionTop}px;
4279
- width: min(320px, calc(100% - ${completionLeft + 8}px));
4280
- max-height: 240px;
4281
- overflow-y: auto;
4282
- z-index: 10;
4283
- box-sizing: border-box;
4284
- border: 1px solid var(--CompletionListBorder, #95a29d);
4285
- background: var(--CompletionListBackground, #282e2f);
4286
- color: var(--CompletionListForeground, white);
4287
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4288
- font-size: 13px;
4289
- line-height: 20px;
4290
- user-select: none;
4291
- }
4082
+ deltaY,
4083
+ headerHeight,
4084
+ itemHeight,
4085
+ x,
4086
+ y
4087
+ } = state;
4088
+ const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4089
+ return handleClick(state, index);
4090
+ };
4292
4091
 
4293
- .ExtensionSearchCompletionItem {
4294
- display: block;
4295
- width: 100%;
4296
- min-height: 20px;
4297
- padding: 0 6px;
4298
- border: 0;
4299
- background: transparent;
4300
- color: inherit;
4301
- font: inherit;
4302
- line-height: inherit;
4303
- text-align: left;
4304
- overflow: hidden;
4305
- text-overflow: ellipsis;
4306
- white-space: nowrap;
4307
- cursor: pointer;
4308
- }
4092
+ const handleClickCurrent = state => {
4093
+ const {
4094
+ focusedIndex
4095
+ } = state;
4096
+ return handleClick(state, focusedIndex);
4097
+ };
4309
4098
 
4310
- .ExtensionSearchCompletionItem:hover {
4311
- background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4312
- }
4099
+ const handleClickCurrentButKeepFocus = state => {
4100
+ const {
4101
+ focusedIndex
4102
+ } = state;
4103
+ return handleClick(state, focusedIndex);
4104
+ };
4313
4105
 
4314
- .ExtensionSearchCompletionItemFocused {
4315
- background: var(--CompletionListItemActiveBackground, #405c50);
4316
- color: var(--CompletionListItemActiveForeground);
4317
- }
4106
+ const show2 = async (uid, menuId, x, y, args) => {
4107
+ await showContextMenu2(uid, menuId, x, y, args);
4108
+ };
4318
4109
 
4319
- .ExtensionSearchCompletionHighlight {
4320
- color: var(--CompletionHighlightForeground, #e1b974);
4321
- font-weight: 700;
4322
- }
4323
- `;
4110
+ const handleClickFilter = async state => {
4111
+ const {
4112
+ headerHeight,
4113
+ uid,
4114
+ width,
4115
+ x,
4116
+ y
4117
+ } = state;
4118
+ const menuX = x + width + 60;
4119
+ const menuHeight = 370;
4120
+ const menuY = y + headerHeight + menuHeight;
4121
+ await show2(uid, ExtensionSearchFilter, menuX, menuY, {
4122
+ menuId: ExtensionSearchFilter
4123
+ });
4124
+ return state;
4324
4125
  };
4325
4126
 
4326
- const renderCss = newState => {
4127
+ const handleContextMenu = async (state, button, eventX, eventY) => {
4128
+ // TODO use focused index when when context menu button is -1 (keyboard)
4327
4129
  const {
4328
- uid
4329
- } = newState;
4330
- const css = getCss(newState);
4331
- return [SetCss, uid, css];
4130
+ deltaY,
4131
+ headerHeight,
4132
+ itemHeight,
4133
+ items,
4134
+ minLineY,
4135
+ uid,
4136
+ x,
4137
+ y
4138
+ } = state;
4139
+ const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4140
+ const index = visibleIndex + minLineY;
4141
+ if (index < 0 || index >= items.length) {
4142
+ return state;
4143
+ }
4144
+ const item = items[index];
4145
+ await show2(uid, ManageExtension, eventX, eventY, {
4146
+ builtin: item.builtin === true,
4147
+ disabled: item.disabled === true,
4148
+ menuId: ManageExtension,
4149
+ status: item.status
4150
+ });
4151
+ return {
4152
+ ...state,
4153
+ focusedIndex: index
4154
+ };
4332
4155
  };
4333
4156
 
4334
- const Extensions$1 = 'extensions';
4157
+ const handleDisableWorkspace = (state, id) => {
4158
+ return state;
4159
+ };
4335
4160
 
4336
- const getSelector = focus => {
4337
- switch (focus) {
4338
- case Input:
4339
- return `[name="${Extensions$1}"]`;
4340
- case List$2:
4341
- return '.ListItems';
4342
- default:
4343
- return '';
4161
+ const handleEnableWorkspace = (state, id) => {
4162
+ return state;
4163
+ };
4164
+
4165
+ const Web = 1;
4166
+ const Electron = 2;
4167
+ const Remote = 3;
4168
+
4169
+ const getAllExtensions$1 = async (assetDir, platform) => {
4170
+ try {
4171
+ return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
4172
+ } catch (error) {
4173
+ if (platform === Web) {
4174
+ return [];
4175
+ }
4176
+ throw error;
4344
4177
  }
4345
4178
  };
4346
- const renderFocus = newState => {
4179
+
4180
+ const getAllExtensions = (assetDir, platform) => {
4181
+ return getAllExtensions$1(assetDir, platform);
4182
+ };
4183
+
4184
+ const getBuiltin = extension => {
4185
+ if (extension === null || typeof extension !== 'object') {
4186
+ return false;
4187
+ }
4347
4188
  const {
4348
- focus,
4349
- uid
4350
- } = newState;
4351
- if (!focus) {
4189
+ builtin,
4190
+ id,
4191
+ isBuiltin
4192
+ } = extension;
4193
+ return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
4194
+ };
4195
+
4196
+ const isString = item => {
4197
+ return typeof item === 'string';
4198
+ };
4199
+
4200
+ const getCategories = extension => {
4201
+ if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
4352
4202
  return [];
4353
4203
  }
4354
- const selector = getSelector(focus);
4355
- return [FocusSelector, uid, selector];
4204
+ return extension.categories.filter(isString);
4356
4205
  };
4357
4206
 
4358
- const renderFocusContext = newState => {
4359
- const {
4360
- uid
4361
- } = newState;
4362
- if (newState.focus === Input) {
4363
- return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4364
- }
4365
- if (newState.focus === List$2) {
4366
- return ['Viewlet.setFocusContext', uid, FocusExtensions];
4207
+ const getDescription = extension => {
4208
+ if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
4209
+ return 'n/a';
4367
4210
  }
4368
- return [];
4211
+ return extension.description;
4369
4212
  };
4370
4213
 
4371
- const ComboBox = 'combobox';
4372
- const Image = 'img';
4373
- const List$1 = 'list';
4374
- const ListBox = 'listbox';
4375
- const ListItem = 'listitem';
4376
- const None = 'none';
4377
- const Option = 'option';
4378
- const ToolBar = 'toolbar';
4214
+ const getDisabled = extension => {
4215
+ return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
4216
+ };
4379
4217
 
4380
- const Actions = 'Actions';
4381
- const ExtensionActions = 'ExtensionActions';
4382
- const ExtensionActionButton = 'ExtensionActionButton';
4383
- const ExtensionActive = 'ExtensionActive';
4384
- const ExtensionHeader = 'ExtensionHeader';
4385
- const ExtensionListItem = 'ExtensionListItem';
4386
- const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
4387
- const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
4388
- const ExtensionListItemDescription = 'ExtensionListItemDescription';
4389
- const ExtensionListItemDetail = 'ExtensionListItemDetail';
4390
- const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
4391
- const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
4392
- const ExtensionListItemFooter = 'ExtensionListItemFooter';
4393
- const ExtensionListItemIcon = 'ExtensionListItemIcon';
4394
- const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
4395
- const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
4396
- const ExtensionListItemName = 'ExtensionListItemName';
4397
- const ExtensionListItemRating = 'ExtensionListItemRating';
4398
- const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
4399
- const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
4400
- const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
4401
- const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
4402
- const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
4403
- const Extensions = 'Extensions';
4404
- const FocusOutline = 'FocusOutline';
4405
- const IconButton = 'IconButton';
4406
- const List = 'List';
4407
- const ListItems = 'ListItems';
4408
- const MaskIcon = 'MaskIcon';
4409
- const MultilineInputBox = 'MultilineInputBox';
4410
- const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
4411
- const ScrollBar = 'ScrollBar';
4412
- const ScrollBarSmall = 'ScrollBarSmall';
4413
- const ScrollBarThumb = 'ScrollBarThumb';
4414
- const ScrollBarThumbActive = 'ScrollBarThumbActive';
4415
- const SearchField = 'SearchField';
4416
- const SearchFieldButton = 'SearchFieldButton';
4417
- const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
4418
- const SearchFieldButtons = 'SearchFieldButtons';
4419
- const SearchFieldContainer = 'SearchFieldContainer';
4420
- const Viewlet = 'Viewlet';
4218
+ const getDownloadCountValue = extension => {
4219
+ if (extension === null || typeof extension !== 'object') {
4220
+ return undefined;
4221
+ }
4222
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4223
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4224
+ return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
4225
+ };
4226
+ const getDownloadCount = extension => {
4227
+ const downloadCount = getDownloadCountValue(extension);
4228
+ if (typeof downloadCount !== 'number') {
4229
+ return 'n/a';
4230
+ }
4231
+ return downloadCount.toLocaleString();
4232
+ };
4421
4233
 
4422
- const getCompletionLabelVirtualDom = (label, highlights) => {
4423
- const dom = [];
4424
- let position = 0;
4425
- for (let i = 0; i < highlights.length; i += 2) {
4426
- const start = highlights[i];
4427
- const end = highlights[i + 1];
4428
- if (position < start) {
4429
- dom.push(text(label.slice(position, start)));
4430
- }
4431
- dom.push({
4432
- childCount: 1,
4433
- className: ExtensionSearchCompletionHighlight,
4434
- name: label,
4435
- type: Span
4436
- }, text(label.slice(start, end)));
4437
- position = end;
4234
+ const getRemoteUrl = (extension, platform, assetDir) => {
4235
+ if (extension === null || typeof extension !== 'object') {
4236
+ return '';
4237
+ }
4238
+ const builtin = 'builtin' in extension && extension.builtin === true;
4239
+ const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
4240
+ const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
4241
+ const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
4242
+ if (platform === Remote || platform === Electron) {
4243
+ if (builtin) {
4244
+ return `${assetDir}/extensions/${id}/${icon}`;
4245
+ }
4246
+ return `/remote/${path}/${icon}`; // TODO support windows paths
4438
4247
  }
4439
- if (position < label.length) {
4440
- dom.push(text(label.slice(position)));
4248
+ if (platform === Web) {
4249
+ return `${path}/${icon}`;
4441
4250
  }
4442
- return dom;
4251
+ return '';
4443
4252
  };
4444
4253
 
4445
- const getRootNodeCount = nodes => {
4446
- let count = 0;
4447
- const remainingChildCounts = [];
4448
- for (const node of nodes) {
4449
- while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
4450
- remainingChildCounts.pop();
4254
+ const getExtensionDefaultIcon = assetDir => {
4255
+ return `${assetDir}/icons/extensionDefaultIcon.png`;
4256
+ };
4257
+ const getExtensionLanguageBasicsIcon = assetDir => {
4258
+ return `${assetDir}/icons/language-icon.svg`;
4259
+ };
4260
+ const getExtensionThemeIcon = assetDir => {
4261
+ return `${assetDir}/icons/theme-icon.png`;
4262
+ };
4263
+
4264
+ const isLanguageBasicsExtension = extension => {
4265
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
4266
+ };
4267
+ const isThemeExtension = extension => {
4268
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
4269
+ };
4270
+ const getExtensionIcon = (extension, platform, assetDir) => {
4271
+ if (extension === null || typeof extension !== 'object') {
4272
+ return getExtensionDefaultIcon(assetDir);
4273
+ }
4274
+ const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
4275
+ const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
4276
+ if (!hasPath || !hasIcon) {
4277
+ if (isLanguageBasicsExtension(extension)) {
4278
+ return getExtensionLanguageBasicsIcon(assetDir);
4451
4279
  }
4452
- if (remainingChildCounts.length === 0) {
4453
- count++;
4454
- } else {
4455
- const lastIndex = remainingChildCounts.length - 1;
4456
- remainingChildCounts[lastIndex]--;
4280
+ if (isThemeExtension(extension)) {
4281
+ return getExtensionThemeIcon(assetDir);
4457
4282
  }
4458
- remainingChildCounts.push(node.childCount);
4283
+ return getExtensionDefaultIcon(assetDir);
4459
4284
  }
4460
- return count;
4285
+ return getRemoteUrl(extension, platform, assetDir);
4461
4286
  };
4462
- const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
4463
- const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
4464
- const focused = index === focusedIndex;
4465
- return [{
4466
- ariaSelected: focused,
4467
- childCount: getRootNodeCount(labelDom),
4468
- className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
4469
- id: `ExtensionSearchCompletion-${index}`,
4470
- name: item.label,
4471
- onPointerDown: HandlePointerDown,
4472
- role: Option,
4473
- type: Button$2
4474
- }, ...labelDom];
4287
+
4288
+ const getIcon = (extension, platform, assetDir) => {
4289
+ return getExtensionIcon(extension, platform, assetDir);
4475
4290
  };
4476
4291
 
4477
- const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
4478
- return [{
4479
- ariaLabel: 'Extension search completions',
4480
- childCount: items.length,
4481
- className: ExtensionSearchCompletionWidget,
4482
- id: 'ExtensionSearchCompletions',
4483
- role: ListBox,
4484
- type: Div
4485
- }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
4292
+ const getId = extension => {
4293
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
4294
+ return 'n/a';
4295
+ }
4296
+ return extension.id;
4486
4297
  };
4487
4298
 
4488
- const Focusable = 0;
4299
+ const getLinked = extension => {
4300
+ if (extension === null || typeof extension !== 'object') {
4301
+ return false;
4302
+ }
4303
+ if ('linked' in extension && extension.linked === true) {
4304
+ return true;
4305
+ }
4306
+ return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
4307
+ };
4489
4308
 
4490
- const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
4491
- const getClassName = enabled => {
4492
- if (enabled) {
4493
- return SearchFieldButton;
4309
+ const getName = extension => {
4310
+ if (extension === null || typeof extension !== 'object') {
4311
+ return 'n/a';
4494
4312
  }
4495
- return disabledClassName;
4313
+ if ('name' in extension && typeof extension.name === 'string' && extension.name) {
4314
+ return extension.name;
4315
+ }
4316
+ if ('id' in extension && typeof extension.id === 'string' && extension.id) {
4317
+ return extension.id;
4318
+ }
4319
+ return 'n/a';
4496
4320
  };
4497
- const getSearchFieldButtonVirtualDom = button => {
4498
- const {
4499
- enabled,
4500
- icon,
4501
- onClick,
4502
- title
4503
- } = button;
4504
- return [{
4505
- childCount: 1,
4506
- className: getClassName(enabled),
4507
- onClick,
4508
- tabIndex: Focusable,
4509
- title,
4510
- type: Button$2
4511
- }, {
4512
- childCount: 0,
4513
- className: mergeClassNames(MaskIcon, icon),
4514
- type: Div
4515
- }];
4321
+
4322
+ const RE_PUBLISHER = /^[a-z\d-]+/;
4323
+ const getPublisher = extension => {
4324
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
4325
+ return 'n/a';
4326
+ }
4327
+ const match = extension.id.match(RE_PUBLISHER);
4328
+ if (!match) {
4329
+ return 'n/a';
4330
+ }
4331
+ return match[0];
4516
4332
  };
4517
4333
 
4518
- const searchFieldNode = {
4519
- childCount: 2,
4520
- className: SearchField,
4521
- role: None,
4522
- type: Div
4334
+ const getRatingValue = extension => {
4335
+ if (extension === null || typeof extension !== 'object') {
4336
+ return undefined;
4337
+ }
4338
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4339
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4340
+ return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
4523
4341
  };
4524
- const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
4525
- // TODO avoid mutation
4526
- const dom = [searchFieldNode, {
4527
- autocapitalize: 'off',
4528
- autocomplete: 'off',
4529
- autocorrect: 'off',
4530
- childCount: 0,
4531
- className: MultilineInputBox,
4532
- inputType: 'search',
4533
- name,
4534
- ...(onBlur && {
4535
- onBlur
4536
- }),
4537
- onFocus,
4538
- onInput,
4539
- placeholder,
4540
- spellcheck: false,
4541
- type: Input$1,
4542
- ...inputProperties
4543
- }, {
4544
- childCount: insideButtons.length,
4545
- className: SearchFieldButtons,
4546
- type: Div
4547
- }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
4548
- if (outsideButtons.length > 0) {
4549
- dom.unshift({
4550
- childCount: 1 + outsideButtons.length,
4551
- className: SearchFieldContainer,
4552
- role: None,
4553
- type: Div
4554
- });
4555
- dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
4342
+ const getRating = extension => {
4343
+ const rating = getRatingValue(extension);
4344
+ if (typeof rating !== 'number') {
4345
+ return 'n/a';
4556
4346
  }
4557
- return dom;
4347
+ return rating.toFixed(1);
4558
4348
  };
4559
4349
 
4560
- const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
4561
- if (!suggestOpen) {
4562
- return [];
4350
+ const getSize = extension => {
4351
+ if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
4352
+ return 0;
4563
4353
  }
4564
- return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
4354
+ return extension.size;
4565
4355
  };
4566
- const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
4567
- const inputProperties = suggestOpen ? {
4568
- ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
4569
- ariaAutoComplete: 'list',
4570
- ariaControls: 'ExtensionSearchCompletions',
4571
- ariaExpanded: true,
4572
- role: ComboBox
4573
- } : {
4574
- ariaAutoComplete: 'list',
4575
- ariaExpanded: false,
4576
- role: ComboBox
4356
+
4357
+ const getStatus = extension => {
4358
+ if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
4359
+ return undefined;
4360
+ }
4361
+ return typeof extension.status === 'string' ? extension.status : undefined;
4362
+ };
4363
+
4364
+ const getUpdatedDate = extension => {
4365
+ if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
4366
+ return 0;
4367
+ }
4368
+ return extension.updatedDate;
4369
+ };
4370
+
4371
+ const normalizeExtension = (extension, platform, assetDir) => {
4372
+ return {
4373
+ builtin: getBuiltin(extension),
4374
+ categories: getCategories(extension),
4375
+ description: getDescription(extension),
4376
+ disabled: getDisabled(extension),
4377
+ downloadCount: getDownloadCount(extension),
4378
+ icon: getIcon(extension, platform, assetDir),
4379
+ id: getId(extension),
4380
+ linked: getLinked(extension),
4381
+ name: getName(extension),
4382
+ publisher: getPublisher(extension),
4383
+ rating: getRating(extension),
4384
+ size: getSize(extension),
4385
+ status: getStatus(extension),
4386
+ updatedDate: getUpdatedDate(extension),
4387
+ uri: ''
4577
4388
  };
4578
- const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
4579
- return [{
4580
- childCount: suggestOpen ? 2 : 1,
4581
- className: ExtensionHeader,
4582
- onContextMenu: HandleHeaderContextMenu,
4583
- type: Div
4584
- }, ...getSearchFieldVirtualDom(Extensions$1, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
4585
4389
  };
4586
4390
 
4587
- const renderHeader = newState => {
4588
- const actions = getInputActions(newState.searchValue.length > 0);
4589
- const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4590
- return ['setHeaderDom', dom];
4391
+ const normalizeExtensions = (extensions, platform, assetDir) => {
4392
+ return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
4393
+ };
4394
+
4395
+ const handleExtensionsChanged = async state => {
4396
+ const {
4397
+ assetDir,
4398
+ platform
4399
+ } = state;
4400
+ const allExtensions = await getAllExtensions(assetDir, platform);
4401
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4402
+ return handleChange({
4403
+ ...state,
4404
+ allExtensions: normalized
4405
+ }, {});
4406
+ };
4407
+
4408
+ const handleFocus = async state => {
4409
+ return {
4410
+ ...state,
4411
+ focus: List$2
4412
+ };
4591
4413
  };
4592
4414
 
4593
- const Extension = 'Extension';
4415
+ const handleHeaderContextMenu = async state => {
4416
+ return state;
4417
+ };
4594
4418
 
4595
- const install = {
4596
- disabled: false,
4597
- label: install$1(),
4598
- onClick: HandleInstall
4419
+ const handleInputFocus = state => {
4420
+ return {
4421
+ ...state,
4422
+ focus: Input
4423
+ };
4599
4424
  };
4600
- const installing = {
4601
- disabled: true,
4602
- label: installing$1(),
4603
- onClick: HandleInstall
4425
+
4426
+ const handleInstall = async (state, id) => {
4427
+ await installExtension(id);
4428
+ return setExtensionStatus(state, id, Enabled);
4604
4429
  };
4605
- const enable = {
4606
- disabled: false,
4607
- label: enable$2(),
4608
- onClick: HandleEnable
4430
+
4431
+ const state = {
4432
+ connected: false
4609
4433
  };
4610
- const disable = {
4611
- disabled: false,
4612
- label: disable$2(),
4613
- onClick: HandleDisable
4434
+ const isConnected = () => {
4435
+ const {
4436
+ connected
4437
+ } = state;
4438
+ return connected;
4614
4439
  };
4615
- const uninstall = {
4616
- disabled: false,
4617
- label: uninstall$1(),
4618
- onClick: HandleUninstall
4440
+ const invoke = (method, ...params) => {
4441
+ return invoke$2(method, ...params);
4619
4442
  };
4620
- const uninstalling = {
4621
- disabled: true,
4622
- label: uninstalling$1(),
4623
- onClick: HandleUninstall
4443
+ const set = rpc => {
4444
+ set$3(rpc);
4445
+ state.connected = true;
4624
4446
  };
4625
- const getExtensionActions = (builtin, disabled, status) => {
4626
- if (status === NotInstalled) {
4627
- return [install];
4628
- }
4629
- if (status === Installing) {
4630
- return [installing];
4631
- }
4632
- if (status === Uninstalling) {
4633
- return builtin ? [] : [uninstalling];
4447
+
4448
+ const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
4449
+ const executeViewletCommand = async (uid, command, ...args) => {
4450
+ const fn = viewletCommandMap[`SearchExtensions.${command}`];
4451
+ if (typeof fn !== 'function') {
4452
+ throw new TypeError(`Viewlet command not found: ${command}`);
4453
+ }
4454
+ await fn(uid, ...args);
4455
+ await invoke$1('Viewlet.requestRender', uid);
4456
+ };
4457
+ const rpc = await create$7({
4458
+ commandMap: {
4459
+ 'Viewlet.executeViewletCommand': executeViewletCommand
4460
+ },
4461
+ messagePort: port
4462
+ });
4463
+ if (setAsRendererProcess) {
4464
+ set(rpc);
4634
4465
  }
4635
- const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
4636
- return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
4637
4466
  };
4638
4467
 
4639
- const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
4640
- const getExtensionActionVirtualDom = (action, id) => {
4641
- return [{
4642
- childCount: 1,
4643
- className,
4644
- disabled: action.disabled,
4645
- name: id,
4646
- onClick: action.onClick,
4647
- type: Button$2
4648
- }, text(action.label)];
4468
+ const handleScrollBarCaptureLost = state => {
4469
+ return {
4470
+ ...state,
4471
+ scrollBarActive: false
4472
+ };
4649
4473
  };
4650
4474
 
4651
- const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
4652
- const actions = getExtensionActions(builtin, disabled, status);
4653
- return [{
4654
- childCount: actions.length,
4655
- className: ExtensionActions,
4656
- type: Div
4657
- }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
4475
+ const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
4476
+ const halfScrollBarHeight = scrollBarHeight / 2;
4477
+ if (relativeY <= halfScrollBarHeight) {
4478
+ // clicked at top
4479
+ return {
4480
+ handleOffset: relativeY,
4481
+ percent: 0
4482
+ };
4483
+ }
4484
+ if (relativeY <= height - halfScrollBarHeight) {
4485
+ // clicked in middle
4486
+ return {
4487
+ handleOffset: halfScrollBarHeight,
4488
+ percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
4489
+ };
4490
+ }
4491
+ // clicked at bottom
4492
+ return {
4493
+ handleOffset: scrollBarHeight - height + relativeY,
4494
+ percent: 1
4495
+ };
4658
4496
  };
4659
4497
 
4660
- const getExtensionListItemClassName = (focused, disabled) => {
4661
- return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
4498
+ const clamp = (num, min, max) => {
4499
+ number(num);
4500
+ number(min);
4501
+ number(max);
4502
+ return Math.min(Math.max(num, min), max);
4662
4503
  };
4663
4504
 
4664
- const getExtensionListItemFooter = hasStatistics => {
4505
+ const setDeltaY = (state, value) => {
4506
+ object(state);
4507
+ number(value);
4508
+ const {
4509
+ deltaY,
4510
+ finalDeltaY,
4511
+ headerHeight,
4512
+ height,
4513
+ itemHeight,
4514
+ items,
4515
+ minimumSliderSize
4516
+ } = state;
4517
+ const listHeight = height - headerHeight;
4518
+ const newDeltaY = clamp(value, 0, finalDeltaY);
4519
+ if (deltaY === newDeltaY) {
4520
+ return state;
4521
+ }
4522
+ // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
4523
+ const minLineY = Math.floor(newDeltaY / itemHeight);
4524
+ const total = items.length;
4525
+ const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
4526
+ const contentHeight = total * itemHeight;
4527
+ const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
4528
+ const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
4665
4529
  return {
4666
- childCount: hasStatistics ? 3 : 2,
4667
- className: ExtensionListItemFooter,
4668
- type: Div
4530
+ ...state,
4531
+ deltaY: newDeltaY,
4532
+ maxLineY,
4533
+ minLineY,
4534
+ scrollBarY
4535
+ };
4536
+ };
4537
+
4538
+ const handleScrollBarClick = (state, eventY) => {
4539
+ // TODO move this to list
4540
+ const {
4541
+ deltaY,
4542
+ finalDeltaY,
4543
+ headerHeight,
4544
+ height,
4545
+ scrollBarHeight,
4546
+ y
4547
+ } = state;
4548
+ const contentHeight = height - headerHeight;
4549
+ const relativeY = eventY - y - headerHeight;
4550
+ const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
4551
+ const diff = relativeY - currentScrollBarY;
4552
+ if (diff >= 0 && diff < scrollBarHeight) {
4553
+ return {
4554
+ ...state,
4555
+ handleOffset: diff,
4556
+ scrollBarActive: true
4557
+ };
4558
+ }
4559
+ const {
4560
+ handleOffset,
4561
+ percent
4562
+ } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
4563
+ const newDeltaY = percent * finalDeltaY;
4564
+ return {
4565
+ ...setDeltaY(state, newDeltaY),
4566
+ handleOffset,
4567
+ scrollBarActive: true
4669
4568
  };
4670
4569
  };
4671
4570
 
4672
- const getExtensionListItemId = focused => {
4673
- if (focused) {
4674
- return `ExtensionActive`;
4571
+ const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
4572
+ if (relativeY <= contentHeight - scrollBarHeight / 2) {
4573
+ // clicked in middle
4574
+ return relativeY / (contentHeight - scrollBarHeight);
4575
+ }
4576
+ // clicked at bottom
4577
+ return 1;
4578
+ };
4579
+ const handleScrollBarMove = (state, eventY) => {
4580
+ const {
4581
+ finalDeltaY,
4582
+ handleOffset,
4583
+ headerHeight,
4584
+ height,
4585
+ scrollBarActive,
4586
+ scrollBarHeight,
4587
+ y
4588
+ } = state;
4589
+ if (!scrollBarActive) {
4590
+ return state;
4591
+ }
4592
+ const relativeY = eventY - y - headerHeight - handleOffset;
4593
+ const contentHeight = height - headerHeight;
4594
+ const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4595
+ const newDeltaY = newPercent * finalDeltaY;
4596
+ return setDeltaY(state, newDeltaY);
4597
+ };
4598
+
4599
+ const handleSettingsButtonClick = async (state, index) => {
4600
+ const {
4601
+ deltaY,
4602
+ headerHeight,
4603
+ itemHeight,
4604
+ items,
4605
+ uid,
4606
+ x,
4607
+ y
4608
+ } = state;
4609
+ const actualIndex = index;
4610
+ if (actualIndex < 0 || actualIndex >= items.length) {
4611
+ return state;
4612
+ }
4613
+
4614
+ // Calculate the position for the context menu
4615
+ // The settings button is at the bottom right of the extension list item
4616
+ const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4617
+ const menuX = x + 200; // Position near the right side of the extension item
4618
+ const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4619
+
4620
+ await show2(uid, ManageExtension, menuX, menuY, {
4621
+ builtin: items[actualIndex].builtin === true,
4622
+ disabled: items[actualIndex].disabled === true,
4623
+ menuId: ManageExtension,
4624
+ status: items[actualIndex].status
4625
+ });
4626
+ return state;
4627
+ };
4628
+
4629
+ const handleUninstall = async (state, id) => {
4630
+ try {
4631
+ await uninstall$2(id);
4632
+ return setExtensionStatus(state, id, NotInstalled);
4633
+ } catch (error) {
4634
+ await showErrorDialog(error);
4635
+ return state;
4675
4636
  }
4676
- return undefined;
4677
4637
  };
4678
4638
 
4679
- const extensionListItemMetadataNode = {
4680
- childCount: 2,
4681
- className: ExtensionListItemMetadata,
4682
- type: Div
4683
- };
4684
- const getStatisticVirtualDom = (label, value, className) => {
4685
- const accessibleLabel = `${label}: ${value}`;
4686
- return [{
4687
- ariaLabel: accessibleLabel,
4688
- childCount: 1,
4689
- className: mergeClassNames(ExtensionListItemStatistic, className),
4690
- title: accessibleLabel,
4691
- type: Span
4692
- }, text(value)];
4693
- };
4694
- const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
4695
- return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
4639
+ const handleWheel = (state, deltaMode, deltaY) => {
4640
+ number(deltaMode);
4641
+ number(deltaY);
4642
+ return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
4696
4643
  };
4697
4644
 
4698
- const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
4699
- if (!hasStatistics) {
4700
- return [];
4645
+ const createExtensionManagementWorkerRpc = async () => {
4646
+ try {
4647
+ const rpc = await create$6({
4648
+ commandMap: {},
4649
+ send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4650
+ });
4651
+ return rpc;
4652
+ } catch (error) {
4653
+ throw new VError(error, `Failed to create extension management rpc`);
4701
4654
  }
4702
- return getExtensionStatisticsVirtualDom(downloadCount, rating);
4703
4655
  };
4704
4656
 
4705
- const getListItemDetail = linked => {
4706
- return {
4707
- childCount: linked ? 4 : 3,
4708
- className: ExtensionListItemDetail,
4709
- type: Div
4710
- };
4711
- };
4712
- const listItemName = {
4713
- childCount: 1,
4714
- className: ExtensionListItemName,
4715
- type: Div
4716
- };
4717
- const listItemDescription = {
4718
- childCount: 1,
4719
- className: ExtensionListItemDescription,
4720
- type: Div
4721
- };
4722
- const listItemAuthorName = {
4723
- childCount: 1,
4724
- className: ExtensionListItemAuthorName,
4725
- type: Div
4726
- };
4727
- const getLinkedIconVirtualDom = linked$1 => {
4728
- if (!linked$1) {
4729
- return [];
4657
+ const initializeExtensionManagementWorker = async () => {
4658
+ try {
4659
+ const rpc = await createExtensionManagementWorkerRpc();
4660
+ set$4(rpc);
4661
+ } catch {
4662
+ // ignore
4730
4663
  }
4731
- const label = linked();
4732
- return [{
4733
- ariaLabel: label,
4734
- childCount: 0,
4735
- className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
4736
- role: Image,
4737
- title: 'Extension is linked',
4738
- type: Div
4739
- }];
4740
4664
  };
4741
- const getExtensionListItemVirtualDom = extension => {
4742
- const {
4743
- builtin = false,
4744
- description,
4745
- disabled = false,
4746
- downloadCount = 'n/a',
4747
- focused,
4748
- icon,
4749
- id,
4750
- linked = false,
4751
- name,
4752
- posInSet,
4753
- publisher,
4754
- rating = 'n/a',
4755
- setSize,
4756
- status
4757
- } = extension;
4758
- const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
4759
- const hasStatistics = !builtin;
4760
- const dom = [{
4761
- ariaPosInSet: posInSet,
4762
- ariaRoleDescription: Extension,
4763
- ariaSetSize: setSize,
4764
- childCount: 2,
4765
- className: getExtensionListItemClassName(focused, disabled),
4766
- id: getExtensionListItemId(focused),
4767
- role: ListItem,
4768
- type: Div
4769
- }, {
4770
- childCount: 0,
4771
- className: ExtensionListItemIcon,
4772
- role: None,
4773
- src: icon,
4774
- type: Img
4775
- }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
4776
- return dom;
4665
+
4666
+ const initialize = async () => {
4667
+ await initializeExtensionManagementWorker();
4777
4668
  };
4778
4669
 
4779
- const getListClassName = focusOutline => {
4780
- const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
4781
- return className;
4670
+ const installAnotherVersion = async state => {
4671
+ await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4672
+ return state;
4782
4673
  };
4783
- const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
4784
- const dom = [{
4785
- ariaLabel: extensions(),
4786
- childCount: visibleExtensions.length,
4787
- className: getListClassName(focusOutline),
4788
- onBlur: HandleBlur,
4789
- onContextmenu: HandleContextMenu,
4790
- onContextMenu: HandleContextMenu,
4791
- onFocus: HandleFocus,
4792
- onPointerDown: HandlePointerDown,
4793
- onTouchEnd: HandleTouchEnd,
4794
- onTouchMove: HandleTouchMove,
4795
- onTouchStart: HandleTouchStart,
4796
- onWheel: HandleWheel,
4797
- role: List$1,
4798
- tabIndex: Focusable,
4799
- type: Div
4800
- }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
4801
- return dom;
4674
+
4675
+ const Small = 1;
4676
+ const Normal = 2;
4677
+ const Large = 3;
4678
+
4679
+ const getViewletSize = width => {
4680
+ if (width < 180) {
4681
+ return Small;
4682
+ }
4683
+ if (width < 768) {
4684
+ return Normal;
4685
+ }
4686
+ return Large;
4802
4687
  };
4803
4688
 
4804
- const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
4805
- const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
4806
- // TODO
4807
- return dom;
4689
+ const getIsFirefox = () => {
4690
+ const globalWithNavigator = globalThis;
4691
+ return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4808
4692
  };
4809
4693
 
4810
- const noExtensionsFoundNode = {
4811
- childCount: 1,
4812
- className: NoExtensionsFoundMessage,
4813
- type: Div
4694
+ const getSavedValue = savedState => {
4695
+ if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4696
+ return savedState.searchValue;
4697
+ }
4698
+ return '';
4814
4699
  };
4815
- const getNoExtensionsFoundVirtualDom = message => {
4816
- return [noExtensionsFoundNode, text(message)];
4700
+ const getSavedDeltaY = savedState => {
4701
+ if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4702
+ return savedState.deltaY;
4703
+ }
4704
+ return 0;
4817
4705
  };
4818
4706
 
4819
- const scrollBarNode = {
4820
- childCount: 1,
4821
- className: mergeClassNames(ScrollBar, ScrollBarSmall),
4822
- onPointerDown: HandleScrollBarPointerDown,
4823
- // TODO support pointercapture event
4824
- type: Div
4825
- };
4826
- const scrollBarThumbNode = {
4827
- childCount: 0,
4828
- className: ScrollBarThumb,
4829
- type: Div
4707
+ const restoreState = savedState => {
4708
+ const searchValue = getSavedValue(savedState);
4709
+ const savedDeltaY = getSavedDeltaY(savedState);
4710
+ return {
4711
+ deltaY: savedDeltaY,
4712
+ searchValue
4713
+ };
4830
4714
  };
4831
- const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
4832
- const shouldShowScrollbar = scrollBarHeight > 0;
4833
- if (!shouldShowScrollbar) {
4834
- return [];
4715
+
4716
+ const loadContentWithContext = async (context, savedState) => {
4717
+ const {
4718
+ uid
4719
+ } = context.getState();
4720
+ const loadToken = getToken(uid);
4721
+ try {
4722
+ const initialState = context.getState();
4723
+ const {
4724
+ assetDir,
4725
+ platform,
4726
+ width
4727
+ } = initialState;
4728
+ const {
4729
+ deltaY,
4730
+ searchValue: restoredSearchValue
4731
+ } = restoreState(savedState);
4732
+ const size = getViewletSize(width);
4733
+ const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4734
+ await context.updateState(state => {
4735
+ if (!state.initial) {
4736
+ return state;
4737
+ }
4738
+ return {
4739
+ ...state,
4740
+ deltaY,
4741
+ initial: false,
4742
+ inputSource: Script,
4743
+ scrollSensitivity,
4744
+ searchValue: restoredSearchValue,
4745
+ size
4746
+ };
4747
+ });
4748
+ const allExtensions = await getAllExtensions(assetDir, platform);
4749
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4750
+ await context.updateState(state => ({
4751
+ ...state,
4752
+ allExtensions: normalized
4753
+ }));
4754
+ await handleChangeWithContext(context, {}, false);
4755
+ } finally {
4756
+ finish(uid, loadToken);
4835
4757
  }
4836
- return [scrollBarNode, scrollBarThumbNode];
4837
4758
  };
4838
4759
 
4839
- const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
4840
- // TODO use normal parameters
4841
- const {
4842
- builtin,
4843
- description,
4844
- disabled,
4845
- downloadCount,
4846
- icon,
4847
- id,
4848
- linked,
4849
- name,
4850
- publisher,
4851
- rating,
4852
- status
4853
- } = item;
4760
+ const openSuggest = state => {
4761
+ const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4762
+ if (completionItems.length === 0) {
4763
+ return state;
4764
+ }
4854
4765
  return {
4855
- builtin,
4856
- description,
4857
- disabled,
4858
- downloadCount,
4859
- focused: i === focusedIndex,
4860
- icon,
4861
- id,
4862
- index: i,
4863
- linked,
4864
- name,
4865
- posInSet: i + 1,
4866
- publisher,
4867
- rating,
4868
- setSize,
4869
- status,
4870
- top: (i - minLineY) * itemHeight - relative
4766
+ ...state,
4767
+ completionFocusedIndex: 0,
4768
+ completionItems,
4769
+ suggestOpen: true
4871
4770
  };
4872
4771
  };
4873
4772
 
4874
- const getVisible = state => {
4773
+ const getCss = state => {
4875
4774
  const {
4775
+ cursorOffset,
4876
4776
  deltaY,
4877
- focusedIndex,
4777
+ headerHeight,
4878
4778
  itemHeight,
4879
- items,
4880
- maxLineY,
4881
- minLineY
4779
+ scrollBarHeight,
4780
+ scrollBarY,
4781
+ width
4882
4782
  } = state;
4883
- const setSize = items.length;
4884
- const visible = [];
4885
- const relative = deltaY % itemHeight;
4886
- for (let i = minLineY; i < maxLineY; i++) {
4887
- const item = items[i];
4888
- visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
4889
- }
4890
- return visible;
4891
- };
4783
+ const relative = -(deltaY % itemHeight);
4784
+ const roundedScrollBarY = Math.round(scrollBarY);
4785
+ const maximumCompletionLeft = Math.max(8, width - 168);
4786
+ const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4787
+ const completionTop = Math.max(0, headerHeight - 10);
4788
+ return `.Extensions .ScrollBarThumb {
4789
+ height: ${scrollBarHeight}px;
4790
+ translate: 0 ${roundedScrollBarY}px;
4791
+ }
4892
4792
 
4893
- const contentNode = {
4894
- childCount: 2,
4895
- className: mergeClassNames(Viewlet, List),
4896
- type: Div
4793
+
4794
+ /* TODO: avoid using negative margin. find a better way*/
4795
+ .ExtensionListItem:nth-child(1) {
4796
+ margin-top: ${relative}px;
4797
+ }
4798
+
4799
+ .ExtensionListItem {
4800
+ box-sizing: border-box;
4801
+ position: relative !important;
4802
+ flex-shrink: 0;
4803
+ }
4804
+
4805
+ .ExtensionListItemLinkedIcon {
4806
+ position: absolute;
4807
+ top: 6px;
4808
+ right: 6px;
4809
+ width: 14px;
4810
+ height: 14px;
4811
+ color: var(--WorkbenchForeground, rgb(188, 190, 190));
4812
+ opacity: 0.8;
4813
+ }
4814
+
4815
+ .ExtensionListItemDisabled:not(.ExtensionActive) {
4816
+ background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4817
+ color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4818
+ }
4819
+
4820
+ .Extensions .ListItems {
4821
+ display: flex;
4822
+ flex-direction: column;
4823
+ gap: 0;
4824
+ overflow-y: hidden;
4825
+ }
4826
+
4827
+ .ExtensionHeader {
4828
+ contain: layout style;
4829
+ position: relative;
4830
+ z-index: 1;
4831
+ }
4832
+
4833
+ .ExtensionListItemFooter {
4834
+ justify-content: flex-end;
4835
+ padding-right: 2px;
4836
+ }
4837
+
4838
+ .ExtensionListItemAuthorName {
4839
+ flex: 1;
4840
+ }
4841
+
4842
+ .ExtensionActions {
4843
+ display: flex;
4844
+ gap: 6px;
4845
+ }
4846
+
4847
+ .ExtensionActionButton {
4848
+ padding: 0 5px;
4849
+ }
4850
+
4851
+ .ExtensionSearchCompletionWidget {
4852
+ position: absolute;
4853
+ left: ${completionLeft}px;
4854
+ top: ${completionTop}px;
4855
+ width: min(320px, calc(100% - ${completionLeft + 8}px));
4856
+ max-height: 240px;
4857
+ overflow-y: auto;
4858
+ z-index: 10;
4859
+ box-sizing: border-box;
4860
+ border: 1px solid var(--CompletionListBorder, #95a29d);
4861
+ background: var(--CompletionListBackground, #282e2f);
4862
+ color: var(--CompletionListForeground, white);
4863
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4864
+ font-size: 13px;
4865
+ line-height: 20px;
4866
+ user-select: none;
4867
+ }
4868
+
4869
+ .ExtensionSearchCompletionItem {
4870
+ display: block;
4871
+ width: 100%;
4872
+ min-height: 20px;
4873
+ padding: 0 6px;
4874
+ border: 0;
4875
+ background: transparent;
4876
+ color: inherit;
4877
+ font: inherit;
4878
+ line-height: inherit;
4879
+ text-align: left;
4880
+ overflow: hidden;
4881
+ text-overflow: ellipsis;
4882
+ white-space: nowrap;
4883
+ cursor: pointer;
4884
+ }
4885
+
4886
+ .ExtensionSearchCompletionItem:hover {
4887
+ background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4888
+ }
4889
+
4890
+ .ExtensionSearchCompletionItemFocused {
4891
+ background: var(--CompletionListItemActiveBackground, #405c50);
4892
+ color: var(--CompletionListItemActiveForeground);
4893
+ }
4894
+
4895
+ .ExtensionSearchCompletionHighlight {
4896
+ color: var(--CompletionHighlightForeground, #e1b974);
4897
+ font-weight: 700;
4898
+ }
4899
+ `;
4897
4900
  };
4898
- const extensionsNode = {
4899
- ariaBusy: false,
4900
- ariaLive: 'polite',
4901
- childCount: 2,
4902
- className: mergeClassNames(Viewlet, Extensions),
4903
- role: None$3,
4904
- type: Div
4901
+
4902
+ const renderCss = newState => {
4903
+ const {
4904
+ uid
4905
+ } = newState;
4906
+ const css = getCss(newState);
4907
+ return [SetCss, uid, css];
4905
4908
  };
4906
- const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
4907
- if (message) {
4908
- return getNoExtensionsFoundVirtualDom(message);
4909
+
4910
+ const getSelector = focus => {
4911
+ switch (focus) {
4912
+ case Input:
4913
+ return `[name="${Extensions}"]`;
4914
+ case List$2:
4915
+ return '.ListItems';
4916
+ default:
4917
+ return '';
4909
4918
  }
4910
- return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
4911
4919
  };
4912
- const getExtensionsViewVirtualDom = state => {
4913
- const visibleExtensions = getVisible(state);
4920
+ const renderFocus = newState => {
4914
4921
  const {
4915
- completionFocusedIndex,
4916
- completionItems,
4917
4922
  focus,
4918
- focusedIndex,
4919
- inputActions,
4920
- message,
4921
- placeholder,
4922
- scrollBarHeight,
4923
- scrollBarY,
4924
- suggestOpen
4925
- } = state;
4926
- const focusOutline = focusedIndex === -1 && focus === List$2;
4927
- return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
4923
+ uid
4924
+ } = newState;
4925
+ if (!focus) {
4926
+ return [];
4927
+ }
4928
+ const selector = getSelector(focus);
4929
+ return [FocusSelector, uid, selector];
4928
4930
  };
4929
4931
 
4930
- const renderItems2 = newState => {
4932
+ const renderFocusContext = newState => {
4931
4933
  const {
4932
- initial,
4933
4934
  uid
4934
4935
  } = newState;
4935
- if (initial) {
4936
- return [SetDom2, uid, []];
4936
+ if (newState.focus === Input) {
4937
+ return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4937
4938
  }
4938
- const dom = getExtensionsViewVirtualDom(newState);
4939
- return [SetDom2, uid, dom];
4939
+ if (newState.focus === List$2) {
4940
+ return ['Viewlet.setFocusContext', uid, FocusExtensions];
4941
+ }
4942
+ return [];
4943
+ };
4944
+
4945
+ const renderHeader = newState => {
4946
+ const actions = getInputActions(newState.searchValue.length > 0);
4947
+ const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4948
+ return ['setHeaderDom', dom];
4940
4949
  };
4941
4950
 
4942
4951
  // TODO cache rendered dom so that it can be used for dom diffing
@@ -4976,7 +4985,7 @@ const renderScrollBar = newState => {
4976
4985
  };
4977
4986
 
4978
4987
  const renderSearchValue = newState => {
4979
- return [/* method */'Viewlet.setValueByName', newState.uid, Extensions$1, newState.searchValue];
4988
+ return [/* method */'Viewlet.setValueByName', newState.uid, Extensions, newState.searchValue];
4980
4989
  };
4981
4990
 
4982
4991
  const titleFilters = [{
@@ -5098,7 +5107,7 @@ const getIconVirtualDom = (icon, type = Div) => {
5098
5107
  return {
5099
5108
  childCount: 0,
5100
5109
  className: mergeClassNames(MaskIcon, `MaskIcon${icon}`),
5101
- role: None,
5110
+ role: None$1,
5102
5111
  type
5103
5112
  };
5104
5113
  };
@@ -5291,6 +5300,23 @@ const selectPreviousCompletion = state => {
5291
5300
  };
5292
5301
  };
5293
5302
 
5303
+ const applyComponentState = (currentState, state) => {
5304
+ if (!state || typeof state !== 'object' || Array.isArray(state)) {
5305
+ throw new TypeError('Extension Search state must be an object');
5306
+ }
5307
+ const {
5308
+ uid
5309
+ } = state;
5310
+ const {
5311
+ uid: currentUid
5312
+ } = currentState;
5313
+ if (uid !== currentUid) {
5314
+ throw new Error(`Extension Search state uid must remain ${currentUid}`);
5315
+ }
5316
+ return state;
5317
+ };
5318
+ const setComponentState = wrapCommand(applyComponentState);
5319
+
5294
5320
  const toggleSuggest = state => {
5295
5321
  return state.suggestOpen ? closeSuggest(state) : openSuggest(state);
5296
5322
  };
@@ -5332,6 +5358,8 @@ const commandMap = {
5332
5358
  'SearchExtensions.focusPreviousPage': wrapCommand(focusPreviousPage),
5333
5359
  'SearchExtensions.getActions': getActions,
5334
5360
  'SearchExtensions.getCommandIds': getCommandIds,
5361
+ 'SearchExtensions.getComponentDom': getComponentDom,
5362
+ 'SearchExtensions.getComponentState': getComponentState,
5335
5363
  'SearchExtensions.getKeyBindings': getKeyBindings,
5336
5364
  'SearchExtensions.getMenuEntries': getMenuEntriesList,
5337
5365
  'SearchExtensions.getMenuEntries2': wrapGetter(getMenuEntries2),
@@ -5377,6 +5405,7 @@ const commandMap = {
5377
5405
  'SearchExtensions.selectIndex': wrapCommand(selectIndex),
5378
5406
  'SearchExtensions.selectNextCompletion': wrapCommand(selectNextCompletion),
5379
5407
  'SearchExtensions.selectPreviousCompletion': wrapCommand(selectPreviousCompletion),
5408
+ 'SearchExtensions.setComponentState': setComponentState,
5380
5409
  'SearchExtensions.setDeltaY': wrapCommand(setDeltaY),
5381
5410
  'SearchExtensions.setExtensionStatus': wrapCommand(setExtensionStatus),
5382
5411
  'SearchExtensions.terminate': terminate,