@lvce-editor/extension-detail-view 3.19.0 → 3.21.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.
@@ -430,7 +430,7 @@ const set$2 = (id, fn) => {
430
430
  const get$2 = id => {
431
431
  return callbacks[id];
432
432
  };
433
- const remove$1 = id => {
433
+ const remove = id => {
434
434
  delete callbacks[id];
435
435
  };
436
436
  let id$1 = 0;
@@ -607,7 +607,7 @@ const resolve = (id, response) => {
607
607
  return;
608
608
  }
609
609
  fn(response);
610
- remove$1(id);
610
+ remove(id);
611
611
  };
612
612
  const E_COMMAND_NOT_FOUND = 'E_COMMAND_NOT_FOUND';
613
613
  const getErrorType = prettyError => {
@@ -857,8 +857,35 @@ const create$1 = () => {
857
857
  newState
858
858
  };
859
859
  },
860
- remove(uid) {
860
+ dispose(uid) {
861
861
  delete states[uid];
862
+ },
863
+ getKeys() {
864
+ return Object.keys(states).map(key => {
865
+ return Number.parseInt(key);
866
+ });
867
+ },
868
+ clear() {
869
+ for (const key of Object.keys(states)) {
870
+ delete states[key];
871
+ }
872
+ },
873
+ wrapCommand(fn) {
874
+ const wrapped = async (uid, ...args) => {
875
+ const {
876
+ newState
877
+ } = states[uid];
878
+ const newerState = await fn(newState, ...args);
879
+ if (newState === newerState) {
880
+ return;
881
+ }
882
+ const latest = states[uid];
883
+ states[uid] = {
884
+ oldState: latest.oldState,
885
+ newState: newerState
886
+ };
887
+ };
888
+ return wrapped;
862
889
  }
863
890
  };
864
891
  };
@@ -866,10 +893,11 @@ const create$1 = () => {
866
893
  const {
867
894
  get: get$1,
868
895
  set: set$1,
869
- remove
896
+ dispose: dispose$1,
897
+ wrapCommand
870
898
  } = create$1();
871
899
 
872
- const create = (uid, uri, width, height, platform, assetDir$1) => {
900
+ const create = (uid, uri, x, y, width, height, platform, assetDir$1) => {
873
901
  const state = {
874
902
  description: '',
875
903
  iconSrc: '',
@@ -927,7 +955,7 @@ const diff2 = uid => {
927
955
  };
928
956
 
929
957
  const dispose = uid => {
930
- remove(uid);
958
+ dispose$1(uid);
931
959
  };
932
960
 
933
961
  const commandIds = ['getMenuEntries', 'handleClickDisable', 'handleClickSize', 'handleClickUninstall', 'handleFeaturesClick', 'handleIconError', 'handleTabsClick', 'renderEventListeners', 'resize', 'saveState', 'selectTab', 'terminate'];
@@ -936,124 +964,6 @@ const getCommandIds = () => {
936
964
  return commandIds;
937
965
  };
938
966
 
939
- const AdditionalDetails = 'AdditionalDetails';
940
- const AdditionalDetailsEntry = 'AdditionalDetailsEntry';
941
- const AdditionalDetailsTitle = 'AdditionalDetailsTitle';
942
- const Aside = 'Aside';
943
- const Button$1 = 'Button';
944
- const ButtonPrimary = 'ButtonPrimary';
945
- const DefaultMarkdown = 'DefaultMarkdown';
946
- const Categories = 'Categories';
947
- const Category = 'Category';
948
- const Changelog$1 = 'Changelog';
949
- const DefinitionListItem = 'DefinitionListItem';
950
- const DefinitionListItemHeading = 'DefinitionListItemHeading';
951
- const DefinitionListItemValue = 'DefinitionListItemValue';
952
- const ExtensionDetail = 'ExtensionDetail';
953
- const ExtensionDetailDescription = 'ExtensionDetailDescription';
954
- const ExtensionDetailHeader = 'ExtensionDetailHeader';
955
- const ExtensionDetailHeaderActions = 'ExtensionDetailHeaderActions';
956
- const ExtensionDetailHeaderDetails = 'ExtensionDetailHeaderDetails';
957
- const ExtensionDetailIcon = 'ExtensionDetailIcon';
958
- const ExtensionDetailName = 'ExtensionDetailName';
959
- const ExtensionDetailPanel = 'ExtensionDetailPanel';
960
- const ExtensionDetailTab = 'ExtensionDetailTab';
961
- const ExtensionDetailTabs = 'ExtensionDetailTabs';
962
- const ExtensionDetailTabSelected = 'ExtensionDetailTabSelected';
963
- const Feature = 'Feature';
964
- const FeatureContent = 'FeatureContent';
965
- const Features$1 = 'Features';
966
- const FeaturesList = 'FeaturesList';
967
- const FeatureWebView = 'FeatureWebView';
968
- const Large$1 = 'Large';
969
- const Sash = 'Sash';
970
- const SashVertical = 'SashVertical';
971
- const MoreInfo = 'MoreInfo';
972
- const MoreInfoEntry = 'MoreInfoEntry';
973
- const MoreInfoEntryKey = 'MoreInfoEntryKey';
974
- const MoreInfoEntryOdd = 'MoreInfoEntryOdd';
975
- const MoreInfoEntryValue = 'MoreInfoEntryValue';
976
- const Normal$1 = 'Normal';
977
- const Resource = 'Resource';
978
- const Resources = 'Resources';
979
- const Small$1 = 'Small';
980
- const Table$1 = 'Table';
981
- const TableCell = 'TableCell';
982
- const TableHeading = 'TableHeading';
983
- const Viewlet = 'Viewlet';
984
-
985
- const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
986
- const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
987
- const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'];
988
- const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'];
989
-
990
- /*
991
- Formats the given number using `Number#toLocaleString`.
992
- - If locale is a string, the value is expected to be a locale-key (for example: `de`).
993
- - If locale is true, the system default locale is used for translation.
994
- - If no value for locale is specified, the number is returned unmodified.
995
- */
996
- const toLocaleString = (number, locale, options) => {
997
- let result = number;
998
- if (typeof locale === 'string' || Array.isArray(locale)) {
999
- result = number.toLocaleString(locale, options);
1000
- } else if (locale === true || options !== undefined) {
1001
- result = number.toLocaleString(undefined, options);
1002
- }
1003
- return result;
1004
- };
1005
- function prettyBytes(number, options) {
1006
- if (!Number.isFinite(number)) {
1007
- throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`);
1008
- }
1009
- options = {
1010
- bits: false,
1011
- binary: false,
1012
- space: true,
1013
- ...options
1014
- };
1015
- const UNITS = options.bits ? options.binary ? BIBIT_UNITS : BIT_UNITS : options.binary ? BIBYTE_UNITS : BYTE_UNITS;
1016
- const separator = options.space ? ' ' : '';
1017
- if (options.signed && number === 0) {
1018
- return ` 0${separator}${UNITS[0]}`;
1019
- }
1020
- const isNegative = number < 0;
1021
- const prefix = isNegative ? '-' : options.signed ? '+' : '';
1022
- if (isNegative) {
1023
- number = -number;
1024
- }
1025
- let localeOptions;
1026
- if (options.minimumFractionDigits !== undefined) {
1027
- localeOptions = {
1028
- minimumFractionDigits: options.minimumFractionDigits
1029
- };
1030
- }
1031
- if (options.maximumFractionDigits !== undefined) {
1032
- localeOptions = {
1033
- maximumFractionDigits: options.maximumFractionDigits,
1034
- ...localeOptions
1035
- };
1036
- }
1037
- if (number < 1) {
1038
- const numberString = toLocaleString(number, options.locale, localeOptions);
1039
- return prefix + numberString + separator + UNITS[0];
1040
- }
1041
- const exponent = Math.min(Math.floor(options.binary ? Math.log(number) / Math.log(1024) : Math.log10(number) / 3), UNITS.length - 1);
1042
- number /= (options.binary ? 1024 : 1000) ** exponent;
1043
- if (!localeOptions) {
1044
- number = number.toPrecision(3);
1045
- }
1046
- const numberString = toLocaleString(Number(number), options.locale, localeOptions);
1047
- const unit = UNITS[exponent];
1048
- return prefix + numberString + separator + unit;
1049
- }
1050
-
1051
- const getDisplaySize = size => {
1052
- return prettyBytes(size, {
1053
- maximumFractionDigits: 1
1054
- });
1055
- };
1056
-
1057
967
  const emptyObject = {};
1058
968
  const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
1059
969
  const i18nString = (key, placeholders = emptyObject) => {
@@ -1144,1328 +1054,1459 @@ const notImplemented = () => {
1144
1054
  return i18nString(NotImplemented);
1145
1055
  };
1146
1056
 
1147
- const A = 53;
1148
- const Button = 1;
1149
- const Div = 4;
1150
- const H1 = 5;
1151
- const H2 = 22;
1152
- const Img = 17;
1153
- const Pre = 51;
1154
- const Table = 9;
1155
- const TBody = 10;
1156
- const Td = 11;
1157
- const Th = 13;
1158
- const THead = 14;
1159
- const Tr = 15;
1057
+ const None = 0;
1160
1058
 
1161
- const mergeClassNames = (...classNames) => {
1162
- return classNames.filter(Boolean).join(' ');
1163
- };
1164
- const Text$1 = 12;
1165
- const text = data => {
1166
- return {
1167
- type: Text$1,
1168
- text: data,
1169
- childCount: 0
1170
- };
1059
+ const getCopyMenuEntry = () => ({
1060
+ id: 'copy',
1061
+ label: copy(),
1062
+ flags: None,
1063
+ command: 'ClipBoard.execCopy'
1064
+ });
1065
+
1066
+ const getImageMenuEntries = props => {
1067
+ if (!props.isImage) {
1068
+ return [];
1069
+ }
1070
+ return [{
1071
+ id: 'openImageInNewTab',
1072
+ label: openImageInNewTab(),
1073
+ flags: None,
1074
+ command: 'Open.openUrl',
1075
+ args: [props.url || '']
1076
+ }, {
1077
+ id: 'saveImageAs',
1078
+ label: saveImageAs(),
1079
+ flags: None,
1080
+ command: 'SaveFileAs.saveFileAs',
1081
+ args: ['image.png', props.url || '']
1082
+ }];
1171
1083
  };
1172
1084
 
1173
- const getChangelogVirtualDom = () => {
1174
- const notImplemented$1 = notImplemented();
1175
- // TODO set tabpanel role
1085
+ const getLinkMenuEntries = props => {
1086
+ if (!props.isLink) {
1087
+ return [];
1088
+ }
1176
1089
  return [{
1177
- type: Div,
1178
- className: Changelog$1,
1179
- childCount: 1
1180
- }, text(notImplemented$1)];
1090
+ id: 'openInNewTab',
1091
+ label: openInNewTab(),
1092
+ flags: None,
1093
+ command: 'Open.openUrl',
1094
+ args: [props.url || '']
1095
+ }];
1181
1096
  };
1182
1097
 
1183
- const TabList = 'tablist';
1184
- const Tab = 'tab';
1185
- const Panel = 'panel';
1098
+ const getMenuEntries = props => [...getLinkMenuEntries(props), ...getImageMenuEntries(props), getCopyMenuEntry()];
1186
1099
 
1187
- const getAdditionalDetailsEntryVirtualDom = (heading, items, renderer) => {
1188
- return [{
1189
- type: Div,
1190
- className: AdditionalDetailsEntry,
1191
- childCount: 2
1192
- }, {
1193
- type: Div,
1194
- className: AdditionalDetailsTitle,
1195
- childCount: 1
1196
- }, text(heading), ...renderer(items)];
1100
+ const handleClickDisable = async state => {
1101
+ return state;
1197
1102
  };
1198
1103
 
1199
- const getCategoryVirtualDom = category => {
1104
+ const selectFeature = async (state, name) => {
1105
+ if (!name) {
1106
+ return state;
1107
+ }
1200
1108
  const {
1201
- label
1202
- } = category;
1203
- return [{
1204
- type: Div,
1205
- className: Category,
1206
- childCount: 1
1207
- }, text(label)];
1109
+ features
1110
+ } = state;
1111
+ const newFeatures = features.map(feature => {
1112
+ if (feature.id === name) {
1113
+ return {
1114
+ ...feature,
1115
+ selected: true
1116
+ };
1117
+ }
1118
+ return {
1119
+ ...feature,
1120
+ selected: false
1121
+ };
1122
+ });
1123
+ return {
1124
+ ...state,
1125
+ selectedFeature: name,
1126
+ features: newFeatures
1127
+ };
1208
1128
  };
1209
1129
 
1210
- const getCategoriesDom = categories => {
1211
- return [{
1212
- type: Div,
1213
- className: Categories,
1214
- childCount: categories.length
1215
- }, ...categories.flatMap(getCategoryVirtualDom)];
1130
+ const handleClickFeatures = async (state, name) => {
1131
+ return selectFeature(state, name);
1216
1132
  };
1217
1133
 
1218
- const parentNode = {
1219
- type: Div,
1220
- className: MoreInfoEntryKey,
1221
- childCount: 1
1134
+ const RendererWorker = 1;
1135
+
1136
+ const rpcs = Object.create(null);
1137
+ const set = (id, rpc) => {
1138
+ rpcs[id] = rpc;
1222
1139
  };
1223
- const getMoreInfoEntryKeyVirtualDom = item => {
1224
- const {
1225
- key
1226
- } = item;
1227
- return [parentNode, text(key)];
1140
+ const get = id => {
1141
+ return rpcs[id];
1228
1142
  };
1229
1143
 
1230
- const getTag = (onClick, code) => {
1231
- if (onClick) {
1232
- return A;
1233
- }
1234
- if (code) {
1235
- // TODO use code tag
1236
- return Div;
1237
- }
1238
- return Div;
1239
- };
1240
- const getClassName = (onClick, code) => {
1241
- if (onClick) {
1242
- return MoreInfoEntryValue + ' Link';
1243
- }
1244
- if (code) {
1245
- return MoreInfoEntryValue + ' Code';
1246
- }
1247
- return MoreInfoEntryValue;
1144
+ const invoke = (method, ...params) => {
1145
+ const rpc = get(RendererWorker);
1146
+ return rpc.invoke(method, ...params);
1248
1147
  };
1249
- const getMoreInfoEntryValueVirtualDom = item => {
1148
+
1149
+ const handleClickSize = async state => {
1250
1150
  const {
1251
- value,
1252
- onClick,
1253
- code
1254
- } = item;
1255
- const type = getTag(onClick, code);
1256
- const className = getClassName(onClick, code);
1257
- return [{
1258
- type: type,
1259
- className,
1260
- childCount: 1,
1261
- onClick
1262
- }, text(value)];
1151
+ uri
1152
+ } = state.extension;
1153
+ await invoke('OpenNativeFolder.openNativeFolder', uri);
1154
+ return state;
1263
1155
  };
1264
1156
 
1265
- const parentNodeEven = {
1266
- type: Div,
1267
- className: MoreInfoEntry,
1268
- childCount: 2
1157
+ const handleClickUninstall = async state => {
1158
+ return state;
1269
1159
  };
1270
- const parentNodeOdd = {
1271
- type: Div,
1272
- className: mergeClassNames(MoreInfoEntry, MoreInfoEntryOdd),
1273
- childCount: 2
1160
+
1161
+ const extensionDefaultIcon = assetDir => {
1162
+ return `${assetDir}/icons/extensionDefaultIcon.png`;
1274
1163
  };
1275
- const getMoreInfoEntryVirtualDom = item => {
1276
- const {
1277
- odd
1278
- } = item;
1279
- const node = odd ? parentNodeOdd : parentNodeEven;
1280
- return [node, ...getMoreInfoEntryKeyVirtualDom(item), ...getMoreInfoEntryValueVirtualDom(item)];
1164
+ const extensionLanguageBasics = assetDir => {
1165
+ return `${assetDir}/icons/language-icon.svg`;
1166
+ };
1167
+ const extensionTheme = assetDir => {
1168
+ return `${assetDir}/icons/theme-icon.png`;
1281
1169
  };
1282
1170
 
1283
- const getMoreInfoVirtualDom = items => {
1284
- return [{
1285
- type: Div,
1286
- className: MoreInfo,
1287
- childCount: items.length
1288
- }, ...items.flatMap(getMoreInfoEntryVirtualDom)];
1171
+ const handleIconError = state => {
1172
+ const {
1173
+ iconSrc,
1174
+ assetDir
1175
+ } = state;
1176
+ if (iconSrc === extensionDefaultIcon(assetDir)) {
1177
+ return state;
1178
+ }
1179
+ return {
1180
+ ...state,
1181
+ iconSrc: extensionDefaultIcon(assetDir)
1182
+ };
1289
1183
  };
1290
1184
 
1291
- const resourceNode = {
1292
- // TODO use link with url
1293
- type: Div,
1294
- className: Resource,
1295
- childCount: 1
1185
+ const Changelog$1 = 'Changelog';
1186
+ const Commands = 'Commands';
1187
+ const Details = 'Details';
1188
+ const Features$1 = 'Features';
1189
+ const JsonValidation = 'JsonValidation';
1190
+ const ProgrammingLanguages = 'ProgrammingLanguages';
1191
+ const Settings = 'Settings';
1192
+ const WebViews = 'WebViews';
1193
+ const Theme = 'Theme';
1194
+
1195
+ const selectTabChangelog = async state => {
1196
+ return {
1197
+ ...state,
1198
+ selectedTab: Changelog$1
1199
+ };
1296
1200
  };
1297
- const getResourceVirtualDom = resource => {
1298
- const {
1299
- label
1300
- } = resource;
1301
- return [resourceNode, text(label)];
1201
+
1202
+ const selectTabDefault = async state => {
1203
+ return state;
1302
1204
  };
1303
1205
 
1304
- const getResourcesVirtualDom = resources => {
1305
- return [{
1306
- type: Div,
1307
- className: Resources,
1308
- childCount: resources.length
1309
- }, ...resources.flatMap(getResourceVirtualDom)];
1206
+ const selectTabDetails = async state => {
1207
+ // TODO load readmo markdown here
1208
+ return {
1209
+ ...state,
1210
+ selectedTab: Details
1211
+ };
1310
1212
  };
1311
1213
 
1312
- const getAdditionalDetailsVirtualDom = (showAdditionalDetails, firstHeading, entries, secondHeading, secondEntries, thirdHeading, categories, fourthHeading, resources) => {
1313
- if (!showAdditionalDetails) {
1314
- return [];
1214
+ const getThemeItemMarkdown = (heading, items) => {
1215
+ let markdown = '';
1216
+ if (items.length > 0) {
1217
+ markdown += `### ${heading}`;
1218
+ markdown += '\n\n';
1219
+ for (const item of items) {
1220
+ markdown += `- ${item.label}`;
1221
+ markdown += '\n';
1222
+ }
1315
1223
  }
1316
- return [{
1317
- type: Div,
1318
- className: Aside,
1319
- childCount: 1
1320
- }, {
1321
- type: Div,
1322
- className: AdditionalDetails,
1323
- tabIndex: 0,
1324
- childCount: 4
1325
- }, ...getAdditionalDetailsEntryVirtualDom(firstHeading, entries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(secondHeading, secondEntries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(thirdHeading, categories, getCategoriesDom), ...getAdditionalDetailsEntryVirtualDom(fourthHeading, resources, getResourcesVirtualDom)];
1224
+ return markdown;
1326
1225
  };
1327
1226
 
1328
- const HandleClickDisable = 'handleClickDisable';
1329
- const HandleClickSize = 'handleClickSize';
1330
- const HandleClickUninstall = 'handleClickUninstall';
1331
- const HandleFeaturesClick = 'handleFeaturesClick';
1332
- const HandleIconError = 'handleIconError';
1333
- const HandleReadmeContextMenu = 'handleReadmeContextMenu';
1334
- const HandleTabsClick = 'handleTabsClick';
1227
+ const getColorThemeMarkdown = themes => {
1228
+ const heading = 'Color Themes';
1229
+ return getThemeItemMarkdown(heading, themes);
1230
+ };
1231
+ const getIconThemeMarkdown = iconThemes => {
1232
+ const heading = 'File Icon Themes';
1233
+ return getThemeItemMarkdown(heading, iconThemes);
1234
+ };
1235
+ const getProductIconThemeMarkdown = iconThemes => {
1236
+ const heading = 'Product Icon Themes';
1237
+ return getThemeItemMarkdown(heading, iconThemes);
1238
+ };
1239
+ const getThemeMarkdown = (themes, iconThemes, productIconThemes) => {
1240
+ let markdown = '';
1241
+ markdown += getColorThemeMarkdown(themes);
1242
+ markdown += getIconThemeMarkdown(iconThemes);
1243
+ markdown += getProductIconThemeMarkdown(productIconThemes);
1244
+ return markdown;
1245
+ };
1335
1246
 
1336
- const getInstallationEntries = (displaySize, extensionId, extensionVersion) => {
1337
- const entries = [{
1338
- key: 'Identifier',
1339
- value: extensionId,
1340
- odd: true,
1341
- code: true
1342
- }, {
1343
- key: 'Version',
1344
- value: extensionVersion,
1345
- code: true
1346
- }, {
1347
- key: 'Last Updated',
1348
- value: 'n/a',
1349
- odd: true
1350
- }, {
1351
- key: 'Size',
1352
- value: displaySize,
1353
- onClick: HandleClickSize
1354
- }];
1355
- return entries;
1247
+ const renderMarkdown = async (markdown, options = {}) => {
1248
+ const html = await invoke('Markdown.renderMarkdown', markdown, options);
1249
+ return html;
1356
1250
  };
1357
1251
 
1358
- const RendererWorker = 1;
1252
+ const selectTab$1 = async state => {
1253
+ const {
1254
+ extension,
1255
+ baseUrl
1256
+ } = state;
1257
+ const {
1258
+ colorThemes,
1259
+ iconThemes,
1260
+ productIconThemes
1261
+ } = extension;
1262
+ // TODO do this depending on featured tab content
1263
+ const markdown = getThemeMarkdown(colorThemes || [], iconThemes || [], productIconThemes || []);
1264
+ const rendered = await renderMarkdown(markdown, {
1265
+ baseUrl
1266
+ });
1267
+ return {
1268
+ ...state,
1269
+ selectedTab: Features$1,
1270
+ selectedFeatureMarkdownDom: rendered
1271
+ };
1272
+ };
1359
1273
 
1360
- const rpcs = Object.create(null);
1361
- const set = (id, rpc) => {
1362
- rpcs[id] = rpc;
1274
+ const getSelectTabHandler = selectedTab => {
1275
+ switch (selectedTab) {
1276
+ case Details:
1277
+ return selectTabDetails;
1278
+ case Features$1:
1279
+ return selectTab$1;
1280
+ case Changelog$1:
1281
+ return selectTabChangelog;
1282
+ default:
1283
+ return selectTabDefault;
1284
+ }
1363
1285
  };
1364
- const get = id => {
1365
- return rpcs[id];
1286
+
1287
+ const selectTab = (state, name) => {
1288
+ const fn = getSelectTabHandler(name);
1289
+ return fn(state);
1366
1290
  };
1367
1291
 
1368
- const invoke = (method, ...params) => {
1369
- const rpc = get(RendererWorker);
1370
- return rpc.invoke(method, ...params);
1292
+ const handleTabsClick = (state, name) => {
1293
+ return selectTab(state, name);
1371
1294
  };
1372
1295
 
1373
- const getMarkdownVirtualDom = async html => {
1374
- string(html);
1375
- const dom = await invoke('Markdown.getVirtualDom', html);
1376
- return dom;
1296
+ const isLanguageBasicsExtension = extension => {
1297
+ return extension.name && extension.name.startsWith('Language Basics');
1377
1298
  };
1378
1299
 
1379
- const getMarketplaceEntries = () => {
1380
- return [{
1381
- key: 'Published',
1382
- value: 'n/a',
1383
- odd: true
1384
- }, {
1385
- key: 'Last Released',
1386
- value: 'n/a'
1387
- }];
1300
+ const isThemeExtension = extension => {
1301
+ return extension.name && extension.name.endsWith(' Theme');
1388
1302
  };
1389
1303
 
1390
- const getDetailsVirtualDom = async (sanitizedReadmeHtml, displaySize, extensionId, extensionVersion, width) => {
1391
- const firstHeading = 'Installation';
1392
- const entries = getInstallationEntries(displaySize, extensionId, extensionVersion);
1393
- const secondHeading = 'Marketplace';
1394
- const secondEntries = getMarketplaceEntries();
1395
- const thirdHeading = 'Categories';
1396
- const categories = [{
1397
- id: 'themes',
1398
- label: 'Themes'
1399
- }];
1400
- const fourthHeading = 'Resources';
1401
- const resources = [{
1402
- label: 'Marketplace',
1403
- url: '#'
1404
- }, {
1405
- label: 'Issues',
1406
- url: '#'
1407
- }, {
1408
- label: 'Repository',
1409
- url: '#'
1410
- }, {
1411
- label: 'License',
1412
- url: '#'
1413
- }];
1414
- const showAdditionalDetailsBreakpoint = 600;
1415
- const showAdditionalDetails = width > showAdditionalDetailsBreakpoint;
1416
- const childCount = showAdditionalDetails ? 2 : 1;
1417
- const dom = [{
1418
- type: Div,
1419
- className: ExtensionDetailPanel,
1420
- childCount: childCount,
1421
- role: Panel
1422
- }, ...(await getMarkdownVirtualDom(sanitizedReadmeHtml)), ...getAdditionalDetailsVirtualDom(showAdditionalDetails, firstHeading, entries, secondHeading, secondEntries, thirdHeading, categories, fourthHeading, resources)];
1423
- return dom;
1424
- };
1425
-
1426
- const Text = 1;
1427
- const Code = 2;
1304
+ const Web = 1;
1305
+ const Electron = 2;
1306
+ const Remote = 3;
1428
1307
 
1429
- const getCommandTableEntry = command => {
1430
- // TODO watch out for command being null/undefined/number/string/array
1431
- const {
1432
- id,
1433
- label
1434
- } = command;
1435
- return [{
1436
- type: Code,
1437
- value: id
1438
- }, {
1439
- type: Text,
1440
- value: label
1441
- }];
1308
+ const getIcon = (extension, platform, assetDir) => {
1309
+ if (!extension) {
1310
+ return extensionDefaultIcon(assetDir);
1311
+ }
1312
+ if (!extension.path || !extension.icon) {
1313
+ if (isLanguageBasicsExtension(extension)) {
1314
+ return extensionLanguageBasics(assetDir);
1315
+ }
1316
+ if (isThemeExtension(extension)) {
1317
+ return extensionTheme(assetDir);
1318
+ }
1319
+ return extensionDefaultIcon(assetDir);
1320
+ }
1321
+ if (platform === Remote || platform === Electron) {
1322
+ if (extension.builtin) {
1323
+ return `${assetDir}/extensions/${extension.id}/${extension.icon}`;
1324
+ }
1325
+ return `/remote/${extension.path}/${extension.icon}`; // TODO support windows paths
1326
+ }
1327
+ return '';
1442
1328
  };
1443
1329
 
1444
- const getCommandTableEntries = extension => {
1445
- const commands = extension.commands || [];
1446
- const rows = commands.map(getCommandTableEntry);
1447
- return {
1448
- headings: ['ID', 'Label'],
1449
- rows
1450
- };
1330
+ const getDescription = extension => {
1331
+ if (!extension || !extension.description) {
1332
+ return 'n/a';
1333
+ }
1334
+ return extension.description;
1451
1335
  };
1452
1336
 
1453
- const getFeatureContentHeadingVirtualDom = heading => {
1454
- return [{
1455
- type: H1,
1456
- childCount: 1
1457
- }, text(heading)];
1337
+ const getName = extension => {
1338
+ if (extension && extension.name) {
1339
+ return extension.name;
1340
+ }
1341
+ if (extension && extension.id) {
1342
+ return extension.id;
1343
+ }
1344
+ return 'n/a';
1458
1345
  };
1459
1346
 
1460
- const getTableHeadingVirtualDom = heading => {
1461
- return [{
1462
- type: Th,
1463
- className: TableHeading,
1464
- childCount: 1
1465
- }, text(heading)];
1347
+ const getAllExtensions = async platform => {
1348
+ if (platform === Web) {
1349
+ return [];
1350
+ }
1351
+ return invoke('ExtensionManagement.getAllExtensions');
1352
+ };
1353
+ const getExtension$1 = async (id, platform) => {
1354
+ // TODO only ask one extension from renderer worker instead of all
1355
+ const allExtensions = await getAllExtensions(platform);
1356
+ for (const extension of allExtensions) {
1357
+ if (extension.id === id) {
1358
+ return extension;
1359
+ }
1360
+ }
1361
+ return undefined;
1466
1362
  };
1467
1363
 
1468
- const getCellCodeVirtualDom = value => {
1469
- return [{
1470
- type: Td,
1471
- className: TableCell,
1472
- childCount: 1
1473
- }, {
1474
- type: Div,
1475
- // TODO use code tag
1476
- childCount: 1
1477
- }, text(value)];
1364
+ const getExtensionNew = async id => {
1365
+ return invoke('ExtensionManagement.getExtension', id);
1366
+ };
1367
+ const getExtension = async (id, platform) => {
1368
+ try {
1369
+ return await getExtensionNew(id);
1370
+ } catch {
1371
+ return getExtension$1(id, platform);
1372
+ }
1478
1373
  };
1479
1374
 
1480
- const getCellTextVirtualDom = value => {
1481
- return [{
1482
- type: Td,
1483
- className: TableCell,
1484
- childCount: 1
1485
- }, text(value)];
1375
+ const getRemoteSrc = uri => {
1376
+ const src = `/remote${uri}`;
1377
+ return src;
1486
1378
  };
1487
1379
 
1488
- const getCellRenderer = type => {
1489
- switch (type) {
1490
- case Code:
1491
- return getCellCodeVirtualDom;
1492
- case Text:
1493
- return getCellTextVirtualDom;
1380
+ const getBaseUrl = (extensionPath, platform) => {
1381
+ switch (platform) {
1382
+ case Remote:
1383
+ case Electron:
1384
+ return getRemoteSrc(extensionPath + '/');
1494
1385
  default:
1495
- throw new Error(`unexpected cell type ${type}`);
1386
+ return extensionPath;
1496
1387
  }
1497
1388
  };
1498
1389
 
1499
- const getCellVirtualDom = entry => {
1500
- const {
1501
- value,
1502
- type
1503
- } = entry;
1504
- const fn = getCellRenderer(type);
1505
- return fn(value);
1390
+ const getExtensionIdFromUri = uri => {
1391
+ const id = uri.slice('extension-detail://'.length);
1392
+ return id;
1506
1393
  };
1507
1394
 
1508
- const getTableRowVirtualDom = entries => {
1509
- return [{
1510
- type: Tr,
1511
- childCount: entries.length
1512
- }, ...entries.flatMap(getCellVirtualDom)];
1395
+ const hasThemes = extension => {
1396
+ return extension && (extension.colorThemes || extension.iconThemes || extension.productIconThemes);
1513
1397
  };
1514
-
1515
- const getTableVirtualDom = tableInfo => {
1516
- const {
1517
- headings,
1518
- rows
1519
- } = tableInfo;
1520
- return [{
1521
- type: Table,
1522
- className: Table$1,
1523
- childCount: 2
1524
- }, {
1525
- type: THead,
1526
- childCount: 1
1527
- }, {
1528
- type: Tr,
1529
- childCount: headings.length
1530
- }, ...headings.flatMap(getTableHeadingVirtualDom), {
1531
- type: TBody,
1532
- childCount: rows.length
1533
- }, ...rows.flatMap(getTableRowVirtualDom)];
1398
+ const hasCommands = extension => {
1399
+ return extension && extension.commands;
1534
1400
  };
1535
-
1536
- // TODO have typed view-model
1537
- const getFeatureCommandsVirtualDom = extension => {
1538
- const heading = commands();
1539
- const tableInfo = getCommandTableEntries(extension);
1540
- return [{
1541
- type: Div,
1542
- className: FeatureContent,
1543
- childCount: 2
1544
- }, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
1401
+ const hasJsonValidation = extension => {
1402
+ return extension && extension.jsonValidation;
1545
1403
  };
1546
-
1547
- const getJsonValidationTableEntry = validation => {
1548
- const {
1549
- fileMatch,
1550
- schema
1551
- } = validation;
1404
+ const hasProgrammingLanguages = extension => {
1405
+ return extension && extension.programmingLanguages;
1406
+ };
1407
+ const hasSettings = extension => {
1408
+ return extension && extension.settings;
1409
+ };
1410
+ const hasWebViews = extension => {
1411
+ return extension && extension.webViews;
1412
+ };
1413
+ const ifElseFeature = (id, label, isEnabled, selectedFeature, extension) => {
1414
+ if (!isEnabled(extension)) {
1415
+ return [];
1416
+ }
1552
1417
  return [{
1553
- type: Code,
1554
- value: fileMatch
1555
- }, {
1556
- type: Code,
1557
- value: schema
1418
+ id,
1419
+ label,
1420
+ selected: selectedFeature === id
1558
1421
  }];
1559
1422
  };
1560
-
1561
- const getJsonValidationTableEntries = extension => {
1562
- const validations = extension.jsonValidation || [];
1563
- const rows = validations.map(getJsonValidationTableEntry);
1564
- return {
1565
- headings: [fileMatch(), schema()],
1566
- rows
1567
- };
1423
+ const getFeatures = (selectedFeature, extension) => {
1424
+ if (!selectedFeature) {
1425
+ selectedFeature = Theme;
1426
+ }
1427
+ const textTheme = theme();
1428
+ const textCommands = commands();
1429
+ const textJsonValidation = jsonValidation();
1430
+ const programmingLanguages$1 = programmingLanguages();
1431
+ const settings$1 = settings();
1432
+ const webViews$1 = webViews();
1433
+ const features = [...ifElseFeature(Theme, textTheme, hasThemes, selectedFeature, extension), ...ifElseFeature(Commands, textCommands, hasCommands, selectedFeature, extension), ...ifElseFeature(JsonValidation, textJsonValidation, hasJsonValidation, selectedFeature, extension), ...ifElseFeature(ProgrammingLanguages, programmingLanguages$1, hasProgrammingLanguages, selectedFeature, extension), ...ifElseFeature(Settings, settings$1, hasSettings, selectedFeature, extension), ...ifElseFeature(WebViews, webViews$1, hasWebViews, selectedFeature, extension)];
1434
+ return features;
1568
1435
  };
1569
1436
 
1570
- const getFeatureJsonValidationVirtualDom = extension => {
1571
- const heading = jsonValidation();
1572
- const tableInfo = getJsonValidationTableEntries(extension);
1573
- return [{
1574
- type: Div,
1575
- className: FeatureContent,
1576
- childCount: 2
1577
- }, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
1437
+ const getFolderSize = async uri => {
1438
+ if (!uri) {
1439
+ throw new VError(`uri is required`);
1440
+ }
1441
+ try {
1442
+ return await invoke('FileSystem.getFolderSize', uri);
1443
+ } catch {
1444
+ return 0;
1445
+ }
1578
1446
  };
1579
1447
 
1580
- const getFeatureNotImplementedVirtualDom = () => {
1581
- const heading = 'Not implemented';
1582
- return [{
1583
- type: Div,
1584
- className: FeatureContent,
1585
- childCount: 1
1586
- }, {
1587
- type: H1,
1588
- childCount: 1
1589
- }, text(heading)];
1590
- };
1448
+ const Small$1 = 1;
1449
+ const Normal$1 = 2;
1450
+ const Large$1 = 3;
1591
1451
 
1592
- const getFeatureProgrammingLanguagesVirtualDom = () => {
1593
- const heading = programmingLanguages();
1594
- // TODO
1595
- return [{
1596
- type: Div,
1597
- className: FeatureContent,
1598
- childCount: 1
1599
- }, ...getFeatureContentHeadingVirtualDom(heading)];
1452
+ const getViewletSize = width => {
1453
+ if (width < 180) {
1454
+ return Small$1;
1455
+ }
1456
+ if (width < 768) {
1457
+ return Normal$1;
1458
+ }
1459
+ return Large$1;
1600
1460
  };
1601
1461
 
1602
- const getSettingsTableEntry = setting => {
1603
- const {
1604
- id,
1605
- label
1606
- } = setting;
1607
- // TODO watch out for null/undefined/number/string/array
1608
- return [{
1609
- type: Text,
1610
- value: id
1611
- }, {
1612
- type: Text,
1613
- value: label
1614
- }];
1462
+ const readFile = async uri => {
1463
+ if (uri.startsWith('http://') || uri.startsWith('https://')) {
1464
+ const response = await fetch(uri);
1465
+ if (!response.ok) {
1466
+ throw new Error(response.statusText);
1467
+ }
1468
+ const result = await response.text();
1469
+ return result;
1470
+ }
1471
+ return invoke('FileSystem.readFile', uri);
1615
1472
  };
1616
1473
 
1617
- const getSettingsTableEntries = extension => {
1618
- const settings = extension.settings || [];
1619
- const rows = settings.map(getSettingsTableEntry);
1620
- const textId = id();
1621
- const textLabel = label();
1622
- return {
1623
- headings: [textId, textLabel],
1624
- rows
1625
- };
1474
+ const ENOENT = 'ENOENT';
1475
+
1476
+ const isEnoentError = error => {
1477
+ return error && error.code === ENOENT;
1626
1478
  };
1627
1479
 
1628
- const getFeatureSettingsVirtualDom = extension => {
1629
- const heading = settings();
1630
- const tableInfo = getSettingsTableEntries(extension);
1631
- return [{
1632
- type: Div,
1633
- className: FeatureContent,
1634
- childCount: 2
1635
- }, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
1480
+ const join = (pathSeparator, ...parts) => {
1481
+ return parts.join(pathSeparator);
1636
1482
  };
1637
1483
 
1638
- const getVirtualDomChildCount = markdownDom => {
1639
- const max = markdownDom.length - 1;
1640
- let stack = [];
1641
- for (let i = max; i >= 0; i--) {
1642
- const element = markdownDom[i];
1643
- if (element.childCount > 0) {
1644
- stack = stack.slice(element.childCount);
1484
+ const loadReadmeContent = async path => {
1485
+ try {
1486
+ const readmeUrl = join('/', path, 'README.md');
1487
+ const readmeContent = await readFile(readmeUrl);
1488
+ return readmeContent;
1489
+ } catch (error) {
1490
+ if (isEnoentError(error)) {
1491
+ return '';
1645
1492
  }
1646
- stack.unshift(element);
1493
+ console.error(new VError(error, 'Failed to load Readme content'));
1494
+ return `${error}`;
1647
1495
  }
1648
- return stack.length;
1649
1496
  };
1650
1497
 
1651
- const getFeatureThemesVirtualDom = async themesHtml => {
1652
- const markdownDom = await getMarkdownVirtualDom(themesHtml);
1653
- const childCount = getVirtualDomChildCount(markdownDom);
1654
- const heading = theme();
1655
- return [{
1656
- type: Div,
1657
- className: FeatureContent,
1658
- childCount: 2
1659
- }, ...getFeatureContentHeadingVirtualDom(heading), {
1660
- type: Div,
1661
- className: DefaultMarkdown,
1662
- childCount
1663
- }, ...markdownDom];
1498
+ const getSavedSelectedFeature = savedState => {
1499
+ if (savedState && typeof savedState === 'object' && 'selectedFeature' in savedState && typeof savedState.selectedFeature === 'string') {
1500
+ return savedState.selectedFeature;
1501
+ }
1502
+ return Details;
1664
1503
  };
1665
1504
 
1666
- const toWebView = rawWebView => {
1667
- const {
1668
- id,
1669
- selector,
1670
- contentSecurityPolicy,
1671
- elements
1672
- } = rawWebView;
1673
- return {
1674
- id,
1675
- selectorString: JSON.stringify(selector),
1676
- contentSecurityPolicyString: JSON.stringify(contentSecurityPolicy),
1677
- elementsString: JSON.stringify(elements, null, 2)
1678
- };
1505
+ const getSavedSelectedTab = savedState => {
1506
+ if (savedState && typeof savedState === 'object' && 'selectedTab' in savedState && typeof savedState.selectedTab === 'string') {
1507
+ return savedState.selectedTab;
1508
+ }
1509
+ return Details;
1679
1510
  };
1680
1511
 
1681
- const getWebViews = extension => {
1682
- const rawWebViews = extension.webViews || [];
1683
- return rawWebViews.map(toWebView);
1512
+ const restoreState = savedState => {
1513
+ const selectedTab = getSavedSelectedTab(savedState);
1514
+ const selectedFeature = getSavedSelectedFeature(savedState);
1515
+ return {
1516
+ selectedFeature,
1517
+ selectedTab
1518
+ };
1684
1519
  };
1685
1520
 
1686
- const heading = {
1687
- type: H2,
1688
- className: DefinitionListItemHeading,
1689
- childCount: 1
1690
- };
1691
- const pre = {
1692
- type: Pre,
1693
- className: DefinitionListItemValue,
1694
- childCount: 1
1695
- };
1696
- const item = {
1697
- type: Div,
1698
- className: DefinitionListItem,
1699
- childCount: 2
1700
- };
1701
- const getWebViewVirtualDom = webView => {
1521
+ const loadContent = async (state, platform, savedState) => {
1702
1522
  const {
1703
- id: id$1,
1704
- selectorString,
1705
- contentSecurityPolicyString,
1706
- elementsString
1707
- } = webView;
1708
- const textId = id();
1709
- const textSelector = selector();
1710
- const textContentSecurityPolicy = contentSecurityPolicy();
1711
- const textElements = elements();
1712
- return [{
1713
- type: Div,
1714
- className: FeatureWebView,
1715
- childCount: 5
1716
- }, item, heading, text(textId), pre, text(id$1), item, heading, text(textSelector), pre, text(selectorString), item, heading, text(textContentSecurityPolicy), pre, text(contentSecurityPolicyString), item, heading, text(textElements), pre, text(elementsString)];
1523
+ uri,
1524
+ width,
1525
+ assetDir
1526
+ } = state;
1527
+ const id = getExtensionIdFromUri(uri);
1528
+ const extension = await getExtension(id, platform);
1529
+ const readmeContent = await loadReadmeContent(extension.path);
1530
+ const baseUrl = getBaseUrl(extension.path, platform);
1531
+ const readmeHtml = await renderMarkdown(readmeContent, {
1532
+ baseUrl
1533
+ });
1534
+ const sanitizedReadmeHtml = readmeHtml;
1535
+ const normalizedReadmeHtml = sanitizedReadmeHtml;
1536
+ const iconSrc = getIcon(extension, platform, assetDir);
1537
+ const description = getDescription(extension);
1538
+ const name = getName(extension);
1539
+ const size = getViewletSize(width);
1540
+ const {
1541
+ selectedFeature,
1542
+ selectedTab
1543
+ } = restoreState(savedState);
1544
+ const features = getFeatures(selectedFeature, extension);
1545
+ const extensionUri = extension.uri || extension.path;
1546
+ const folderSize = await getFolderSize(extensionUri);
1547
+ const entries = [{
1548
+ key: 'Identifier',
1549
+ value: 'abc'
1550
+ }, {
1551
+ key: 'Version',
1552
+ value: '1.9.5'
1553
+ }, {
1554
+ key: 'Last Updated',
1555
+ value: 'n/a'
1556
+ }];
1557
+ const secondEntries = [{
1558
+ key: 'Published',
1559
+ value: 'n/a'
1560
+ }, {
1561
+ key: 'Last Released',
1562
+ value: 'n/a'
1563
+ }];
1564
+ const categories = [{
1565
+ id: 'themes',
1566
+ label: 'Themes'
1567
+ }];
1568
+ const resources = [{
1569
+ label: 'Marketplace',
1570
+ url: '#'
1571
+ }, {
1572
+ label: 'Issues',
1573
+ url: '#'
1574
+ }, {
1575
+ label: 'Repository',
1576
+ url: '#'
1577
+ }, {
1578
+ label: 'License',
1579
+ url: '#'
1580
+ }];
1581
+ return {
1582
+ ...state,
1583
+ selectedTab,
1584
+ sanitizedReadmeHtml: normalizedReadmeHtml,
1585
+ iconSrc,
1586
+ name,
1587
+ description,
1588
+ sizeOnDisk: size,
1589
+ entries,
1590
+ secondEntries,
1591
+ categories,
1592
+ resources,
1593
+ extension,
1594
+ baseUrl,
1595
+ features,
1596
+ folderSize
1597
+ };
1717
1598
  };
1718
1599
 
1719
- const getFeatureWebViewsVirtualDom = extension => {
1720
- const webViews$1 = getWebViews(extension);
1721
- const heading = webViews();
1722
- return [{
1723
- type: Div,
1724
- className: FeatureContent,
1725
- childCount: 2
1726
- }, ...getFeatureContentHeadingVirtualDom(heading), {
1727
- type: Div,
1728
- childCount: webViews$1.length
1729
- }, ...webViews$1.flatMap(getWebViewVirtualDom)];
1600
+ const loadContent2 = async (state, savedState) => {
1601
+ return loadContent(state, state.platform, savedState);
1730
1602
  };
1731
1603
 
1604
+ const AdditionalDetails = 'AdditionalDetails';
1605
+ const AdditionalDetailsEntry = 'AdditionalDetailsEntry';
1606
+ const AdditionalDetailsTitle = 'AdditionalDetailsTitle';
1607
+ const Aside = 'Aside';
1608
+ const Button$1 = 'Button';
1609
+ const ButtonPrimary = 'ButtonPrimary';
1610
+ const DefaultMarkdown = 'DefaultMarkdown';
1611
+ const Categories = 'Categories';
1612
+ const Category = 'Category';
1732
1613
  const Changelog = 'Changelog';
1733
- const Commands = 'Commands';
1734
- const Details = 'Details';
1614
+ const DefinitionListItem = 'DefinitionListItem';
1615
+ const DefinitionListItemHeading = 'DefinitionListItemHeading';
1616
+ const DefinitionListItemValue = 'DefinitionListItemValue';
1617
+ const ExtensionDetail = 'ExtensionDetail';
1618
+ const ExtensionDetailDescription = 'ExtensionDetailDescription';
1619
+ const ExtensionDetailHeader = 'ExtensionDetailHeader';
1620
+ const ExtensionDetailHeaderActions = 'ExtensionDetailHeaderActions';
1621
+ const ExtensionDetailHeaderDetails = 'ExtensionDetailHeaderDetails';
1622
+ const ExtensionDetailIcon = 'ExtensionDetailIcon';
1623
+ const ExtensionDetailName = 'ExtensionDetailName';
1624
+ const ExtensionDetailPanel = 'ExtensionDetailPanel';
1625
+ const ExtensionDetailTab = 'ExtensionDetailTab';
1626
+ const ExtensionDetailTabs = 'ExtensionDetailTabs';
1627
+ const ExtensionDetailTabSelected = 'ExtensionDetailTabSelected';
1628
+ const Feature = 'Feature';
1629
+ const FeatureContent = 'FeatureContent';
1735
1630
  const Features = 'Features';
1736
- const JsonValidation = 'JsonValidation';
1737
- const ProgrammingLanguages = 'ProgrammingLanguages';
1738
- const Settings = 'Settings';
1739
- const WebViews = 'WebViews';
1740
- const Theme = 'Theme';
1631
+ const FeaturesList = 'FeaturesList';
1632
+ const FeatureWebView = 'FeatureWebView';
1633
+ const Large = 'Large';
1634
+ const Sash = 'Sash';
1635
+ const SashVertical = 'SashVertical';
1636
+ const MoreInfo = 'MoreInfo';
1637
+ const MoreInfoEntry = 'MoreInfoEntry';
1638
+ const MoreInfoEntryKey = 'MoreInfoEntryKey';
1639
+ const MoreInfoEntryOdd = 'MoreInfoEntryOdd';
1640
+ const MoreInfoEntryValue = 'MoreInfoEntryValue';
1641
+ const Normal = 'Normal';
1642
+ const Resource = 'Resource';
1643
+ const Resources = 'Resources';
1644
+ const Small = 'Small';
1645
+ const Table$1 = 'Table';
1646
+ const TableCell = 'TableCell';
1647
+ const TableHeading = 'TableHeading';
1648
+ const Viewlet = 'Viewlet';
1741
1649
 
1742
- const getFeatureContentVirtualDom = async (features, themesHtml, selectedFeature, extension) => {
1743
- switch (selectedFeature) {
1744
- case Theme:
1745
- return await getFeatureThemesVirtualDom(themesHtml);
1746
- case Commands:
1747
- return getFeatureCommandsVirtualDom(extension);
1748
- case JsonValidation:
1749
- return getFeatureJsonValidationVirtualDom(extension);
1750
- case ProgrammingLanguages:
1751
- return getFeatureProgrammingLanguagesVirtualDom();
1752
- case Settings:
1753
- return getFeatureSettingsVirtualDom(extension);
1754
- case WebViews:
1755
- return getFeatureWebViewsVirtualDom(extension);
1756
- default:
1757
- return getFeatureNotImplementedVirtualDom();
1650
+ const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
1651
+ const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
1652
+ const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'];
1653
+ const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'];
1654
+
1655
+ /*
1656
+ Formats the given number using `Number#toLocaleString`.
1657
+ - If locale is a string, the value is expected to be a locale-key (for example: `de`).
1658
+ - If locale is true, the system default locale is used for translation.
1659
+ - If no value for locale is specified, the number is returned unmodified.
1660
+ */
1661
+ const toLocaleString = (number, locale, options) => {
1662
+ let result = number;
1663
+ if (typeof locale === 'string' || Array.isArray(locale)) {
1664
+ result = number.toLocaleString(locale, options);
1665
+ } else if (locale === true || options !== undefined) {
1666
+ result = number.toLocaleString(undefined, options);
1758
1667
  }
1668
+ return result;
1759
1669
  };
1670
+ function prettyBytes(number, options) {
1671
+ if (!Number.isFinite(number)) {
1672
+ throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`);
1673
+ }
1674
+ options = {
1675
+ bits: false,
1676
+ binary: false,
1677
+ space: true,
1678
+ ...options
1679
+ };
1680
+ const UNITS = options.bits ? options.binary ? BIBIT_UNITS : BIT_UNITS : options.binary ? BIBYTE_UNITS : BYTE_UNITS;
1681
+ const separator = options.space ? ' ' : '';
1682
+ if (options.signed && number === 0) {
1683
+ return ` 0${separator}${UNITS[0]}`;
1684
+ }
1685
+ const isNegative = number < 0;
1686
+ const prefix = isNegative ? '-' : options.signed ? '+' : '';
1687
+ if (isNegative) {
1688
+ number = -number;
1689
+ }
1690
+ let localeOptions;
1691
+ if (options.minimumFractionDigits !== undefined) {
1692
+ localeOptions = {
1693
+ minimumFractionDigits: options.minimumFractionDigits
1694
+ };
1695
+ }
1696
+ if (options.maximumFractionDigits !== undefined) {
1697
+ localeOptions = {
1698
+ maximumFractionDigits: options.maximumFractionDigits,
1699
+ ...localeOptions
1700
+ };
1701
+ }
1702
+ if (number < 1) {
1703
+ const numberString = toLocaleString(number, options.locale, localeOptions);
1704
+ return prefix + numberString + separator + UNITS[0];
1705
+ }
1706
+ const exponent = Math.min(Math.floor(options.binary ? Math.log(number) / Math.log(1024) : Math.log10(number) / 3), UNITS.length - 1);
1707
+ number /= (options.binary ? 1024 : 1000) ** exponent;
1708
+ if (!localeOptions) {
1709
+ number = number.toPrecision(3);
1710
+ }
1711
+ const numberString = toLocaleString(Number(number), options.locale, localeOptions);
1712
+ const unit = UNITS[exponent];
1713
+ return prefix + numberString + separator + unit;
1714
+ }
1760
1715
 
1761
- const getFeatureListItemVirtualDom = feature => {
1762
- const {
1763
- label,
1764
- selected,
1765
- id
1766
- } = feature;
1767
- const className = selected ? 'Feature FeatureSelected' : Feature;
1768
- return [{
1769
- // TODO use role list item or tab
1770
- type: Button,
1771
- name: id,
1772
- className,
1773
- childCount: 1
1774
- }, text(label)];
1716
+ const getDisplaySize = size => {
1717
+ return prettyBytes(size, {
1718
+ maximumFractionDigits: 1
1719
+ });
1775
1720
  };
1776
1721
 
1777
- const getFeatureListVirtualDom = features => {
1778
- return [{
1779
- // TODO use either list or tabs role
1780
- type: Div,
1781
- className: FeaturesList,
1782
- childCount: features.length,
1783
- onClick: HandleFeaturesClick
1784
- }, ...features.flatMap(getFeatureListItemVirtualDom)];
1785
- };
1722
+ const A = 53;
1723
+ const Button = 1;
1724
+ const Div = 4;
1725
+ const H1 = 5;
1726
+ const H2 = 22;
1727
+ const Img = 17;
1728
+ const Pre = 51;
1729
+ const Table = 9;
1730
+ const TBody = 10;
1731
+ const Td = 11;
1732
+ const Th = 13;
1733
+ const THead = 14;
1734
+ const Tr = 15;
1786
1735
 
1787
- const getFeaturesVirtualDom = async (features, themesHtml, selectedFeature, extension) => {
1788
- if (features.length === 0) {
1789
- const none$1 = none();
1790
- return [{
1791
- type: Div,
1792
- className: Features$1,
1793
- childCount: 3
1794
- }, text(none$1)];
1795
- }
1796
- return [{
1797
- type: Div,
1798
- className: Features$1,
1799
- childCount: 3
1800
- }, ...getFeatureListVirtualDom(features), {
1801
- type: Div,
1802
- className: mergeClassNames(Sash, SashVertical),
1803
- childCount: 0
1804
- }, ...(await getFeatureContentVirtualDom(features, themesHtml, selectedFeature, extension))];
1736
+ const mergeClassNames = (...classNames) => {
1737
+ return classNames.filter(Boolean).join(' ');
1805
1738
  };
1806
-
1807
- const getExtensionDetailContentVirtualDom = async (sanitizedReadmeHtml, themesHtml, selectedTab, features, displaySize, extensionId, extensionVersion, selectedFeature, extension, width) => {
1808
- switch (selectedTab) {
1809
- case Details:
1810
- return await getDetailsVirtualDom(sanitizedReadmeHtml, displaySize, extensionId, extensionVersion, width);
1811
- case Features:
1812
- return await getFeaturesVirtualDom(features, themesHtml, selectedFeature, extension);
1813
- case Changelog:
1814
- return getChangelogVirtualDom();
1815
- default:
1816
- return [];
1817
- }
1739
+ const Text$1 = 12;
1740
+ const text = data => {
1741
+ return {
1742
+ type: Text$1,
1743
+ text: data,
1744
+ childCount: 0
1745
+ };
1818
1746
  };
1819
1747
 
1820
- const getButtonVirtualDom = (message, onClick) => {
1748
+ const getChangelogVirtualDom = () => {
1749
+ const notImplemented$1 = notImplemented();
1750
+ // TODO set tabpanel role
1821
1751
  return [{
1822
- type: Button,
1823
- className: Button$1 + ' ' + ButtonPrimary,
1824
- onClick,
1825
- childCount: 1
1826
- }, text(message)];
1827
- };
1828
-
1829
- const getExtensionDetailHeaderActionsVirtualDom = () => {
1830
- const dom = [{
1831
1752
  type: Div,
1832
- className: ExtensionDetailHeaderActions,
1833
- childCount: 2
1834
- }, ...getButtonVirtualDom('Disable', HandleClickDisable), ...getButtonVirtualDom('Uninstall', HandleClickUninstall)];
1835
- return dom;
1836
- };
1837
-
1838
- const getExtensionDetailHeaderVirtualDom = extensionDetail => {
1839
- const {
1840
- name,
1841
- iconSrc,
1842
- description
1843
- } = extensionDetail;
1844
- const dom = [{
1845
- type: Div,
1846
- className: ExtensionDetailHeader,
1847
- childCount: 2
1848
- }, {
1849
- type: Img,
1850
- className: ExtensionDetailIcon,
1851
- alt: '',
1852
- draggable: false,
1853
- childCount: 0,
1854
- src: iconSrc
1855
- }, {
1856
- type: Div,
1857
- className: ExtensionDetailHeaderDetails,
1858
- childCount: 3
1859
- }, {
1860
- type: Div,
1861
- className: ExtensionDetailName,
1753
+ className: Changelog,
1862
1754
  childCount: 1
1863
- }, text(name), {
1864
- type: Div,
1865
- className: ExtensionDetailDescription,
1866
- childCount: 1
1867
- }, text(description), ...getExtensionDetailHeaderActionsVirtualDom()];
1868
- return dom;
1755
+ }, text(notImplemented$1)];
1869
1756
  };
1870
1757
 
1871
- const hasThemes = extension => {
1872
- return extension && (extension.colorThemes || extension.iconThemes || extension.productIconThemes);
1873
- };
1874
- const hasCommands = extension => {
1875
- return extension && extension.commands;
1876
- };
1877
- const hasJsonValidation = extension => {
1878
- return extension && extension.jsonValidation;
1879
- };
1880
- const hasProgrammingLanguages = extension => {
1881
- return extension && extension.programmingLanguages;
1882
- };
1883
- const hasSettings = extension => {
1884
- return extension && extension.settings;
1885
- };
1886
- const hasWebViews = extension => {
1887
- return extension && extension.webViews;
1888
- };
1889
- const ifElseFeature = (id, label, isEnabled, selectedFeature, extension) => {
1890
- if (!isEnabled(extension)) {
1891
- return [];
1892
- }
1893
- return [{
1894
- id,
1895
- label,
1896
- selected: selectedFeature === id
1897
- }];
1898
- };
1899
- const getFeatures = (selectedFeature, extension) => {
1900
- if (!selectedFeature) {
1901
- selectedFeature = Theme;
1902
- }
1903
- const textTheme = theme();
1904
- const textCommands = commands();
1905
- const textJsonValidation = jsonValidation();
1906
- const programmingLanguages$1 = programmingLanguages();
1907
- const settings$1 = settings();
1908
- const webViews$1 = webViews();
1909
- const features = [...ifElseFeature(Theme, textTheme, hasThemes, selectedFeature, extension), ...ifElseFeature(Commands, textCommands, hasCommands, selectedFeature, extension), ...ifElseFeature(JsonValidation, textJsonValidation, hasJsonValidation, selectedFeature, extension), ...ifElseFeature(ProgrammingLanguages, programmingLanguages$1, hasProgrammingLanguages, selectedFeature, extension), ...ifElseFeature(Settings, settings$1, hasSettings, selectedFeature, extension), ...ifElseFeature(WebViews, webViews$1, hasWebViews, selectedFeature, extension)];
1910
- return features;
1911
- };
1758
+ const TabList = 'tablist';
1759
+ const Tab = 'tab';
1760
+ const Panel = 'panel';
1912
1761
 
1913
- const getTabs = selectedTab => {
1914
- const tabs = [{
1915
- label: 'Details',
1916
- name: Details,
1917
- selected: selectedTab === Details
1918
- }, {
1919
- label: 'Features',
1920
- name: Features,
1921
- selected: selectedTab === Features
1762
+ const getAdditionalDetailsEntryVirtualDom = (heading, items, renderer) => {
1763
+ return [{
1764
+ type: Div,
1765
+ className: AdditionalDetailsEntry,
1766
+ childCount: 2
1922
1767
  }, {
1923
- label: 'Changelog',
1924
- name: Changelog,
1925
- selected: selectedTab === Changelog
1926
- }];
1927
- return tabs;
1768
+ type: Div,
1769
+ className: AdditionalDetailsTitle,
1770
+ childCount: 1
1771
+ }, text(heading), ...renderer(items)];
1928
1772
  };
1929
1773
 
1930
- const selectedClassName = mergeClassNames(ExtensionDetailTab, ExtensionDetailTabSelected);
1931
- const defaultClassName = ExtensionDetailTab;
1932
- const getTabVirtualDom = tab => {
1774
+ const getCategoryVirtualDom = category => {
1933
1775
  const {
1934
- label,
1935
- selected,
1936
- name
1937
- } = tab;
1938
- const className = selected ? selectedClassName : defaultClassName;
1939
- const ariaSelected = selected ? 'true' : 'false';
1776
+ label
1777
+ } = category;
1940
1778
  return [{
1941
- type: Button,
1942
- role: Tab,
1943
- name,
1944
- className,
1945
- childCount: 1,
1946
- tabIndex: -1,
1947
- ariaSelected
1779
+ type: Div,
1780
+ className: Category,
1781
+ childCount: 1
1948
1782
  }, text(label)];
1949
1783
  };
1950
1784
 
1951
- const getTabsVirtualDom = tabs => {
1785
+ const getCategoriesDom = categories => {
1952
1786
  return [{
1953
1787
  type: Div,
1954
- className: ExtensionDetailTabs,
1955
- childCount: tabs.length,
1956
- role: TabList,
1957
- onClick: HandleTabsClick,
1958
- tabIndex: 0
1959
- }, ...tabs.flatMap(getTabVirtualDom)];
1788
+ className: Categories,
1789
+ childCount: categories.length
1790
+ }, ...categories.flatMap(getCategoryVirtualDom)];
1960
1791
  };
1961
1792
 
1962
- const Small = 1;
1963
- const Normal = 2;
1964
- const Large = 3;
1793
+ const parentNode$1 = {
1794
+ type: Div,
1795
+ className: MoreInfoEntryKey,
1796
+ childCount: 1
1797
+ };
1798
+ const getMoreInfoEntryKeyVirtualDom = item => {
1799
+ const {
1800
+ key
1801
+ } = item;
1802
+ return [parentNode$1, text(key)];
1803
+ };
1965
1804
 
1966
- const getViewletSize = width => {
1967
- if (width < 180) {
1968
- return Small;
1805
+ const getTag = (onClick, code) => {
1806
+ if (onClick) {
1807
+ return A;
1969
1808
  }
1970
- if (width < 768) {
1971
- return Normal;
1809
+ if (code) {
1810
+ // TODO use code tag
1811
+ return Div;
1972
1812
  }
1973
- return Large;
1813
+ return Div;
1974
1814
  };
1975
-
1976
- const getClassNames = size => {
1977
- switch (size) {
1978
- case Small:
1979
- return Small$1;
1980
- case Normal:
1981
- return Normal$1;
1982
- case Large:
1983
- return Large$1;
1984
- default:
1985
- return '';
1815
+ const getClassName = (onClick, code) => {
1816
+ if (onClick) {
1817
+ return MoreInfoEntryValue + ' Link';
1818
+ }
1819
+ if (code) {
1820
+ return MoreInfoEntryValue + ' Code';
1986
1821
  }
1822
+ return MoreInfoEntryValue;
1823
+ };
1824
+ const getMoreInfoEntryValueVirtualDom = item => {
1825
+ const {
1826
+ value,
1827
+ onClick,
1828
+ code
1829
+ } = item;
1830
+ const type = getTag(onClick, code);
1831
+ const className = getClassName(onClick, code);
1832
+ return [{
1833
+ type: type,
1834
+ className,
1835
+ childCount: 1,
1836
+ onClick
1837
+ }, text(value)];
1987
1838
  };
1988
1839
 
1989
- const getExtensionDetailVirtualDom = async (extensionDetail, sanitizedReadmeHtml, selectedTab, newState) => {
1990
- // TODO move this to view model so that rendering occurs like
1991
- // 1. state
1992
- // 2. view model
1993
- // 3. virtual dom
1994
- // 4. dom
1995
- const themesHtml = newState?.selectedFeatureMarkdownDom || '';
1996
- const selectedFeature = newState?.selectedFeature || '';
1997
- const extension = newState?.extension || {};
1998
- const features = getFeatures(selectedFeature, extension);
1999
- const size = newState.folderSize || 0;
2000
- const extensionId = newState?.extension?.id || 'n/a';
2001
- const extensionVersion = newState?.extension?.version || 'n/a';
2002
- const displaySize = getDisplaySize(size);
2003
- const width = newState?.width || 500;
2004
- const tabs = getTabs(selectedTab);
2005
- const sizeValue = getViewletSize(newState?.width || 0);
2006
- const sizeClass = getClassNames(sizeValue);
2007
- const dom = [{
1840
+ const parentNodeEven = {
1841
+ type: Div,
1842
+ className: MoreInfoEntry,
1843
+ childCount: 2
1844
+ };
1845
+ const parentNodeOdd = {
1846
+ type: Div,
1847
+ className: mergeClassNames(MoreInfoEntry, MoreInfoEntryOdd),
1848
+ childCount: 2
1849
+ };
1850
+ const getMoreInfoEntryVirtualDom = item => {
1851
+ const {
1852
+ odd
1853
+ } = item;
1854
+ const node = odd ? parentNodeOdd : parentNodeEven;
1855
+ return [node, ...getMoreInfoEntryKeyVirtualDom(item), ...getMoreInfoEntryValueVirtualDom(item)];
1856
+ };
1857
+
1858
+ const getMoreInfoVirtualDom = items => {
1859
+ return [{
2008
1860
  type: Div,
2009
- className: mergeClassNames(Viewlet, ExtensionDetail, sizeClass),
2010
- childCount: 3
2011
- }, ...getExtensionDetailHeaderVirtualDom(extensionDetail), ...getTabsVirtualDom(tabs), ...(await getExtensionDetailContentVirtualDom(sanitizedReadmeHtml, themesHtml, selectedTab, features, displaySize, extensionId, extensionVersion, selectedFeature, extension, width))];
2012
- return dom;
1861
+ className: MoreInfo,
1862
+ childCount: items.length
1863
+ }, ...items.flatMap(getMoreInfoEntryVirtualDom)];
2013
1864
  };
2014
1865
 
2015
- const getExtensionDetailVirtualDom2 = async uid => {
1866
+ const resourceNode = {
1867
+ // TODO use link with url
1868
+ type: Div,
1869
+ className: Resource,
1870
+ childCount: 1
1871
+ };
1872
+ const getResourceVirtualDom = resource => {
2016
1873
  const {
2017
- newState
2018
- } = get$1(uid);
2019
- return await getExtensionDetailVirtualDom(newState, newState.sanitizedReadmeHtml, newState.selectedTab, newState);
1874
+ label
1875
+ } = resource;
1876
+ return [resourceNode, text(label)];
2020
1877
  };
2021
1878
 
2022
- const None = 0;
2023
-
2024
- const getCopyMenuEntry = () => ({
2025
- id: 'copy',
2026
- label: copy(),
2027
- flags: None,
2028
- command: 'ClipBoard.execCopy'
2029
- });
1879
+ const getResourcesVirtualDom = resources => {
1880
+ return [{
1881
+ type: Div,
1882
+ className: Resources,
1883
+ childCount: resources.length
1884
+ }, ...resources.flatMap(getResourceVirtualDom)];
1885
+ };
2030
1886
 
2031
- const getImageMenuEntries = props => {
2032
- if (!props.isImage) {
1887
+ const getAdditionalDetailsVirtualDom = (showAdditionalDetails, firstHeading, entries, secondHeading, secondEntries, thirdHeading, categories, fourthHeading, resources) => {
1888
+ if (!showAdditionalDetails) {
2033
1889
  return [];
2034
1890
  }
2035
1891
  return [{
2036
- id: 'openImageInNewTab',
2037
- label: openImageInNewTab(),
2038
- flags: None,
2039
- command: 'Open.openUrl',
2040
- args: [props.url || '']
1892
+ type: Div,
1893
+ className: Aside,
1894
+ childCount: 1
1895
+ }, {
1896
+ type: Div,
1897
+ className: AdditionalDetails,
1898
+ tabIndex: 0,
1899
+ childCount: 4
1900
+ }, ...getAdditionalDetailsEntryVirtualDom(firstHeading, entries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(secondHeading, secondEntries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(thirdHeading, categories, getCategoriesDom), ...getAdditionalDetailsEntryVirtualDom(fourthHeading, resources, getResourcesVirtualDom)];
1901
+ };
1902
+
1903
+ const HandleClickDisable = 'handleClickDisable';
1904
+ const HandleClickSize = 'handleClickSize';
1905
+ const HandleClickUninstall = 'handleClickUninstall';
1906
+ const HandleFeaturesClick = 'handleFeaturesClick';
1907
+ const HandleIconError = 'handleIconError';
1908
+ const HandleReadmeContextMenu = 'handleReadmeContextMenu';
1909
+ const HandleTabsClick = 'handleTabsClick';
1910
+
1911
+ const getInstallationEntries = (displaySize, extensionId, extensionVersion) => {
1912
+ const entries = [{
1913
+ key: 'Identifier',
1914
+ value: extensionId,
1915
+ odd: true,
1916
+ code: true
1917
+ }, {
1918
+ key: 'Version',
1919
+ value: extensionVersion,
1920
+ code: true
1921
+ }, {
1922
+ key: 'Last Updated',
1923
+ value: 'n/a',
1924
+ odd: true
1925
+ }, {
1926
+ key: 'Size',
1927
+ value: displaySize,
1928
+ onClick: HandleClickSize
1929
+ }];
1930
+ return entries;
1931
+ };
1932
+
1933
+ const getMarkdownVirtualDom = async html => {
1934
+ string(html);
1935
+ const dom = await invoke('Markdown.getVirtualDom', html);
1936
+ return dom;
1937
+ };
1938
+
1939
+ const getMarketplaceEntries = () => {
1940
+ return [{
1941
+ key: 'Published',
1942
+ value: 'n/a',
1943
+ odd: true
1944
+ }, {
1945
+ key: 'Last Released',
1946
+ value: 'n/a'
1947
+ }];
1948
+ };
1949
+
1950
+ const getDetailsVirtualDom = async (sanitizedReadmeHtml, displaySize, extensionId, extensionVersion, width) => {
1951
+ const firstHeading = 'Installation';
1952
+ const entries = getInstallationEntries(displaySize, extensionId, extensionVersion);
1953
+ const secondHeading = 'Marketplace';
1954
+ const secondEntries = getMarketplaceEntries();
1955
+ const thirdHeading = 'Categories';
1956
+ const categories = [{
1957
+ id: 'themes',
1958
+ label: 'Themes'
1959
+ }];
1960
+ const fourthHeading = 'Resources';
1961
+ const resources = [{
1962
+ label: 'Marketplace',
1963
+ url: '#'
1964
+ }, {
1965
+ label: 'Issues',
1966
+ url: '#'
1967
+ }, {
1968
+ label: 'Repository',
1969
+ url: '#'
2041
1970
  }, {
2042
- id: 'saveImageAs',
2043
- label: saveImageAs(),
2044
- flags: None,
2045
- command: 'SaveFileAs.saveFileAs',
2046
- args: ['image.png', props.url || '']
1971
+ label: 'License',
1972
+ url: '#'
2047
1973
  }];
1974
+ const showAdditionalDetailsBreakpoint = 600;
1975
+ const showAdditionalDetails = width > showAdditionalDetailsBreakpoint;
1976
+ const childCount = showAdditionalDetails ? 2 : 1;
1977
+ const dom = [{
1978
+ type: Div,
1979
+ className: ExtensionDetailPanel,
1980
+ childCount: childCount,
1981
+ role: Panel
1982
+ }, ...(await getMarkdownVirtualDom(sanitizedReadmeHtml)), ...getAdditionalDetailsVirtualDom(showAdditionalDetails, firstHeading, entries, secondHeading, secondEntries, thirdHeading, categories, fourthHeading, resources)];
1983
+ return dom;
2048
1984
  };
2049
1985
 
2050
- const getLinkMenuEntries = props => {
2051
- if (!props.isLink) {
2052
- return [];
2053
- }
1986
+ const Text = 1;
1987
+ const Code = 2;
1988
+
1989
+ const getCommandTableEntry = command => {
1990
+ // TODO watch out for command being null/undefined/number/string/array
1991
+ const {
1992
+ id,
1993
+ label
1994
+ } = command;
2054
1995
  return [{
2055
- id: 'openInNewTab',
2056
- label: openInNewTab(),
2057
- flags: None,
2058
- command: 'Open.openUrl',
2059
- args: [props.url || '']
1996
+ type: Code,
1997
+ value: id
1998
+ }, {
1999
+ type: Text,
2000
+ value: label
2060
2001
  }];
2061
2002
  };
2062
2003
 
2063
- const getMenuEntries = props => [...getLinkMenuEntries(props), ...getImageMenuEntries(props), getCopyMenuEntry()];
2064
-
2065
- const handleClickDisable = async state => {
2066
- return state;
2067
- };
2068
-
2069
- const selectFeature = async (state, name) => {
2070
- if (!name) {
2071
- return state;
2072
- }
2073
- const {
2074
- features
2075
- } = state;
2076
- const newFeatures = features.map(feature => {
2077
- if (feature.id === name) {
2078
- return {
2079
- ...feature,
2080
- selected: true
2081
- };
2082
- }
2083
- return {
2084
- ...feature,
2085
- selected: false
2086
- };
2087
- });
2004
+ const getCommandTableEntries = extension => {
2005
+ const commands = extension.commands || [];
2006
+ const rows = commands.map(getCommandTableEntry);
2007
+ const id$1 = id();
2008
+ const label$1 = label();
2088
2009
  return {
2089
- ...state,
2090
- selectedFeature: name,
2091
- features: newFeatures
2010
+ headings: [id$1, label$1],
2011
+ rows
2092
2012
  };
2093
2013
  };
2094
2014
 
2095
- const handleClickFeatures = async (state, name) => {
2096
- return selectFeature(state, name);
2015
+ const getFeatureContentHeadingVirtualDom = heading => {
2016
+ return [{
2017
+ type: H1,
2018
+ childCount: 1
2019
+ }, text(heading)];
2097
2020
  };
2098
2021
 
2099
- const handleClickSize = async state => {
2100
- const {
2101
- uri
2102
- } = state.extension;
2103
- await invoke('OpenNativeFolder.openNativeFolder', uri);
2104
- return state;
2022
+ const getTableHeadingVirtualDom = heading => {
2023
+ return [{
2024
+ type: Th,
2025
+ className: TableHeading,
2026
+ childCount: 1
2027
+ }, text(heading)];
2105
2028
  };
2106
2029
 
2107
- const handleClickUninstall = async state => {
2108
- return state;
2030
+ const getCellCodeVirtualDom = value => {
2031
+ return [{
2032
+ type: Td,
2033
+ className: TableCell,
2034
+ childCount: 1
2035
+ }, {
2036
+ type: Div,
2037
+ // TODO use code tag
2038
+ childCount: 1
2039
+ }, text(value)];
2109
2040
  };
2110
2041
 
2111
- const extensionDefaultIcon = assetDir => {
2112
- return `${assetDir}/icons/extensionDefaultIcon.png`;
2113
- };
2114
- const extensionLanguageBasics = assetDir => {
2115
- return `${assetDir}/icons/language-icon.svg`;
2116
- };
2117
- const extensionTheme = assetDir => {
2118
- return `${assetDir}/icons/theme-icon.png`;
2042
+ const getCellTextVirtualDom = value => {
2043
+ return [{
2044
+ type: Td,
2045
+ className: TableCell,
2046
+ childCount: 1
2047
+ }, text(value)];
2119
2048
  };
2120
2049
 
2121
- const handleIconError = state => {
2122
- const {
2123
- iconSrc,
2124
- assetDir
2125
- } = state;
2126
- if (iconSrc === extensionDefaultIcon(assetDir)) {
2127
- return state;
2050
+ const getCellRenderer = type => {
2051
+ switch (type) {
2052
+ case Code:
2053
+ return getCellCodeVirtualDom;
2054
+ case Text:
2055
+ return getCellTextVirtualDom;
2056
+ default:
2057
+ throw new Error(`unexpected cell type ${type}`);
2128
2058
  }
2129
- return {
2130
- ...state,
2131
- iconSrc: extensionDefaultIcon(assetDir)
2132
- };
2133
- };
2134
-
2135
- const selectTabChangelog = async state => {
2136
- return {
2137
- ...state,
2138
- selectedTab: Changelog
2139
- };
2140
2059
  };
2141
2060
 
2142
- const selectTabDefault = async state => {
2143
- return state;
2061
+ const getCellVirtualDom = entry => {
2062
+ const {
2063
+ value,
2064
+ type
2065
+ } = entry;
2066
+ const fn = getCellRenderer(type);
2067
+ return fn(value);
2144
2068
  };
2145
2069
 
2146
- const selectTabDetails = async state => {
2147
- // TODO load readmo markdown here
2148
- return {
2149
- ...state,
2150
- selectedTab: Details
2151
- };
2070
+ const getTableRowVirtualDom = entries => {
2071
+ return [{
2072
+ type: Tr,
2073
+ childCount: entries.length
2074
+ }, ...entries.flatMap(getCellVirtualDom)];
2152
2075
  };
2153
2076
 
2154
- const getThemeItemMarkdown = (heading, items) => {
2155
- let markdown = '';
2156
- if (items.length > 0) {
2157
- markdown += `### ${heading}`;
2158
- markdown += '\n\n';
2159
- for (const item of items) {
2160
- markdown += `- ${item.label}`;
2161
- markdown += '\n';
2162
- }
2163
- }
2164
- return markdown;
2077
+ const getTableVirtualDom = tableInfo => {
2078
+ const {
2079
+ headings,
2080
+ rows
2081
+ } = tableInfo;
2082
+ return [{
2083
+ type: Table,
2084
+ className: Table$1,
2085
+ childCount: 2
2086
+ }, {
2087
+ type: THead,
2088
+ childCount: 1
2089
+ }, {
2090
+ type: Tr,
2091
+ childCount: headings.length
2092
+ }, ...headings.flatMap(getTableHeadingVirtualDom), {
2093
+ type: TBody,
2094
+ childCount: rows.length
2095
+ }, ...rows.flatMap(getTableRowVirtualDom)];
2165
2096
  };
2166
2097
 
2167
- const getColorThemeMarkdown = themes => {
2168
- const heading = 'Color Themes';
2169
- return getThemeItemMarkdown(heading, themes);
2170
- };
2171
- const getIconThemeMarkdown = iconThemes => {
2172
- const heading = 'File Icon Themes';
2173
- return getThemeItemMarkdown(heading, iconThemes);
2174
- };
2175
- const getProductIconThemeMarkdown = iconThemes => {
2176
- const heading = 'Product Icon Themes';
2177
- return getThemeItemMarkdown(heading, iconThemes);
2178
- };
2179
- const getThemeMarkdown = (themes, iconThemes, productIconThemes) => {
2180
- let markdown = '';
2181
- markdown += getColorThemeMarkdown(themes);
2182
- markdown += getIconThemeMarkdown(iconThemes);
2183
- markdown += getProductIconThemeMarkdown(productIconThemes);
2184
- return markdown;
2098
+ // TODO have typed view-model
2099
+ const getFeatureCommandsVirtualDom = extension => {
2100
+ const heading = commands();
2101
+ const tableInfo = getCommandTableEntries(extension);
2102
+ return [{
2103
+ type: Div,
2104
+ className: FeatureContent,
2105
+ childCount: 2
2106
+ }, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
2185
2107
  };
2186
2108
 
2187
- const renderMarkdown = async (markdown, options = {}) => {
2188
- const html = await invoke('Markdown.renderMarkdown', markdown, options);
2189
- return html;
2109
+ const getJsonValidationTableEntry = validation => {
2110
+ const {
2111
+ fileMatch,
2112
+ schema
2113
+ } = validation;
2114
+ return [{
2115
+ type: Code,
2116
+ value: fileMatch
2117
+ }, {
2118
+ type: Code,
2119
+ value: schema
2120
+ }];
2190
2121
  };
2191
2122
 
2192
- const selectTab$1 = async state => {
2193
- const {
2194
- extension,
2195
- baseUrl
2196
- } = state;
2197
- const {
2198
- colorThemes,
2199
- iconThemes,
2200
- productIconThemes
2201
- } = extension;
2202
- // TODO do this depending on featured tab content
2203
- const markdown = getThemeMarkdown(colorThemes || [], iconThemes || [], productIconThemes || []);
2204
- const rendered = await renderMarkdown(markdown, {
2205
- baseUrl
2206
- });
2123
+ const getJsonValidationTableEntries = extension => {
2124
+ const validations = extension.jsonValidation || [];
2125
+ const rows = validations.map(getJsonValidationTableEntry);
2207
2126
  return {
2208
- ...state,
2209
- selectedTab: Features,
2210
- selectedFeatureMarkdownDom: rendered
2127
+ headings: [fileMatch(), schema()],
2128
+ rows
2211
2129
  };
2212
2130
  };
2213
2131
 
2214
- const getSelectTabHandler = selectedTab => {
2215
- switch (selectedTab) {
2216
- case Details:
2217
- return selectTabDetails;
2218
- case Features:
2219
- return selectTab$1;
2220
- case Changelog:
2221
- return selectTabChangelog;
2222
- default:
2223
- return selectTabDefault;
2224
- }
2132
+ const parentNode = {
2133
+ type: Div,
2134
+ className: FeatureContent,
2135
+ childCount: 2
2136
+ };
2137
+ const getFeatureJsonValidationVirtualDom = extension => {
2138
+ const heading = jsonValidation();
2139
+ const tableInfo = getJsonValidationTableEntries(extension);
2140
+ return [parentNode, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
2225
2141
  };
2226
2142
 
2227
- const selectTab = (state, name) => {
2228
- const fn = getSelectTabHandler(name);
2229
- return fn(state);
2143
+ const getFeatureNotImplementedVirtualDom = () => {
2144
+ const heading = 'Not implemented';
2145
+ return [{
2146
+ type: Div,
2147
+ className: FeatureContent,
2148
+ childCount: 1
2149
+ }, {
2150
+ type: H1,
2151
+ childCount: 1
2152
+ }, text(heading)];
2230
2153
  };
2231
2154
 
2232
- const handleTabsClick = (state, name) => {
2233
- return selectTab(state, name);
2155
+ const getFeatureProgrammingLanguagesVirtualDom = () => {
2156
+ const heading = programmingLanguages();
2157
+ // TODO
2158
+ return [{
2159
+ type: Div,
2160
+ className: FeatureContent,
2161
+ childCount: 1
2162
+ }, ...getFeatureContentHeadingVirtualDom(heading)];
2234
2163
  };
2235
2164
 
2236
- const isLanguageBasicsExtension = extension => {
2237
- return extension.name && extension.name.startsWith('Language Basics');
2165
+ const getSettingsTableEntry = setting => {
2166
+ const {
2167
+ id,
2168
+ label
2169
+ } = setting;
2170
+ // TODO watch out for null/undefined/number/string/array
2171
+ return [{
2172
+ type: Text,
2173
+ value: id
2174
+ }, {
2175
+ type: Text,
2176
+ value: label
2177
+ }];
2238
2178
  };
2239
2179
 
2240
- const isThemeExtension = extension => {
2241
- return extension.name && extension.name.endsWith(' Theme');
2180
+ const getSettingsTableEntries = extension => {
2181
+ const settings = extension.settings || [];
2182
+ const rows = settings.map(getSettingsTableEntry);
2183
+ const textId = id();
2184
+ const textLabel = label();
2185
+ return {
2186
+ headings: [textId, textLabel],
2187
+ rows
2188
+ };
2242
2189
  };
2243
2190
 
2244
- const Web = 1;
2245
- const Electron = 2;
2246
- const Remote = 3;
2191
+ const getFeatureSettingsVirtualDom = extension => {
2192
+ const heading = settings();
2193
+ const tableInfo = getSettingsTableEntries(extension);
2194
+ return [{
2195
+ type: Div,
2196
+ className: FeatureContent,
2197
+ childCount: 2
2198
+ }, ...getFeatureContentHeadingVirtualDom(heading), ...getTableVirtualDom(tableInfo)];
2199
+ };
2247
2200
 
2248
- const getIcon = (extension, platform, assetDir) => {
2249
- if (!extension) {
2250
- return extensionDefaultIcon(assetDir);
2251
- }
2252
- if (!extension.path || !extension.icon) {
2253
- if (isLanguageBasicsExtension(extension)) {
2254
- return extensionLanguageBasics(assetDir);
2255
- }
2256
- if (isThemeExtension(extension)) {
2257
- return extensionTheme(assetDir);
2258
- }
2259
- return extensionDefaultIcon(assetDir);
2260
- }
2261
- if (platform === Remote || platform === Electron) {
2262
- if (extension.builtin) {
2263
- return `${assetDir}/extensions/${extension.id}/${extension.icon}`;
2201
+ const getVirtualDomChildCount = markdownDom => {
2202
+ const max = markdownDom.length - 1;
2203
+ let stack = [];
2204
+ for (let i = max; i >= 0; i--) {
2205
+ const element = markdownDom[i];
2206
+ if (element.childCount > 0) {
2207
+ stack = stack.slice(element.childCount);
2264
2208
  }
2265
- return `/remote/${extension.path}/${extension.icon}`; // TODO support windows paths
2209
+ stack.unshift(element);
2266
2210
  }
2267
- return '';
2211
+ return stack.length;
2268
2212
  };
2269
2213
 
2270
- const getDescription = extension => {
2271
- if (!extension || !extension.description) {
2272
- return 'n/a';
2273
- }
2274
- return extension.description;
2214
+ const getFeatureThemesVirtualDom = async themesHtml => {
2215
+ const markdownDom = await getMarkdownVirtualDom(themesHtml);
2216
+ const childCount = getVirtualDomChildCount(markdownDom);
2217
+ const heading = theme();
2218
+ return [{
2219
+ type: Div,
2220
+ className: FeatureContent,
2221
+ childCount: 2
2222
+ }, ...getFeatureContentHeadingVirtualDom(heading), {
2223
+ type: Div,
2224
+ className: DefaultMarkdown,
2225
+ childCount
2226
+ }, ...markdownDom];
2275
2227
  };
2276
2228
 
2277
- const getName = extension => {
2278
- if (extension && extension.name) {
2279
- return extension.name;
2280
- }
2281
- if (extension && extension.id) {
2282
- return extension.id;
2283
- }
2284
- return 'n/a';
2229
+ const toWebView = rawWebView => {
2230
+ const {
2231
+ id,
2232
+ selector,
2233
+ contentSecurityPolicy,
2234
+ elements
2235
+ } = rawWebView;
2236
+ return {
2237
+ id,
2238
+ selectorString: JSON.stringify(selector),
2239
+ contentSecurityPolicyString: JSON.stringify(contentSecurityPolicy),
2240
+ elementsString: JSON.stringify(elements, null, 2)
2241
+ };
2285
2242
  };
2286
2243
 
2287
- const getAllExtensions = async platform => {
2288
- if (platform === Web) {
2289
- return [];
2290
- }
2291
- return invoke('ExtensionManagement.getAllExtensions');
2292
- };
2293
- const getExtension$1 = async (id, platform) => {
2294
- // TODO only ask one extension from renderer worker instead of all
2295
- const allExtensions = await getAllExtensions(platform);
2296
- for (const extension of allExtensions) {
2297
- if (extension.id === id) {
2298
- return extension;
2299
- }
2300
- }
2301
- return undefined;
2244
+ const getWebViews = extension => {
2245
+ const rawWebViews = extension.webViews || [];
2246
+ return rawWebViews.map(toWebView);
2302
2247
  };
2303
2248
 
2304
- const getExtensionNew = async id => {
2305
- return invoke('ExtensionManagement.getExtension', id);
2249
+ const heading = {
2250
+ type: H2,
2251
+ className: DefinitionListItemHeading,
2252
+ childCount: 1
2306
2253
  };
2307
- const getExtension = async (id, platform) => {
2308
- try {
2309
- return await getExtensionNew(id);
2310
- } catch {
2311
- return getExtension$1(id, platform);
2312
- }
2254
+ const pre = {
2255
+ type: Pre,
2256
+ className: DefinitionListItemValue,
2257
+ childCount: 1
2313
2258
  };
2314
-
2315
- const getRemoteSrc = uri => {
2316
- const src = `/remote${uri}`;
2317
- return src;
2259
+ const item = {
2260
+ type: Div,
2261
+ className: DefinitionListItem,
2262
+ childCount: 2
2318
2263
  };
2319
-
2320
- const getBaseUrl = (extensionPath, platform) => {
2321
- switch (platform) {
2322
- case Remote:
2323
- case Electron:
2324
- return getRemoteSrc(extensionPath + '/');
2325
- default:
2326
- return extensionPath;
2327
- }
2264
+ const getWebViewVirtualDom = webView => {
2265
+ const {
2266
+ id: id$1,
2267
+ selectorString,
2268
+ contentSecurityPolicyString,
2269
+ elementsString
2270
+ } = webView;
2271
+ const textId = id();
2272
+ const textSelector = selector();
2273
+ const textContentSecurityPolicy = contentSecurityPolicy();
2274
+ const textElements = elements();
2275
+ return [{
2276
+ type: Div,
2277
+ className: FeatureWebView,
2278
+ childCount: 5
2279
+ }, item, heading, text(textId), pre, text(id$1), item, heading, text(textSelector), pre, text(selectorString), item, heading, text(textContentSecurityPolicy), pre, text(contentSecurityPolicyString), item, heading, text(textElements), pre, text(elementsString)];
2328
2280
  };
2329
2281
 
2330
- const getFolderSize = async uri => {
2331
- try {
2332
- return await invoke('FileSystem.getFolderSize', uri);
2333
- } catch {
2334
- return 0;
2335
- }
2282
+ const getFeatureWebViewsVirtualDom = extension => {
2283
+ const webViews$1 = getWebViews(extension);
2284
+ const heading = webViews();
2285
+ return [{
2286
+ type: Div,
2287
+ className: FeatureContent,
2288
+ childCount: 2
2289
+ }, ...getFeatureContentHeadingVirtualDom(heading), {
2290
+ type: Div,
2291
+ childCount: webViews$1.length
2292
+ }, ...webViews$1.flatMap(getWebViewVirtualDom)];
2336
2293
  };
2337
2294
 
2338
- const readFile = async uri => {
2339
- return invoke('FileSystem.readFile', uri);
2295
+ const getFeatureContentVirtualDom = async (features, themesHtml, selectedFeature, extension) => {
2296
+ switch (selectedFeature) {
2297
+ case Theme:
2298
+ return await getFeatureThemesVirtualDom(themesHtml);
2299
+ case Commands:
2300
+ return getFeatureCommandsVirtualDom(extension);
2301
+ case JsonValidation:
2302
+ return getFeatureJsonValidationVirtualDom(extension);
2303
+ case ProgrammingLanguages:
2304
+ return getFeatureProgrammingLanguagesVirtualDom();
2305
+ case Settings:
2306
+ return getFeatureSettingsVirtualDom(extension);
2307
+ case WebViews:
2308
+ return getFeatureWebViewsVirtualDom(extension);
2309
+ default:
2310
+ return getFeatureNotImplementedVirtualDom();
2311
+ }
2340
2312
  };
2341
2313
 
2342
- const ENOENT = 'ENOENT';
2343
-
2344
- const isEnoentError = error => {
2345
- return error && error.code === ENOENT;
2314
+ const getFeatureListItemVirtualDom = feature => {
2315
+ const {
2316
+ label,
2317
+ selected,
2318
+ id
2319
+ } = feature;
2320
+ const className = selected ? 'Feature FeatureSelected' : Feature;
2321
+ return [{
2322
+ // TODO use role list item or tab
2323
+ type: Button,
2324
+ name: id,
2325
+ className,
2326
+ childCount: 1
2327
+ }, text(label)];
2346
2328
  };
2347
2329
 
2348
- const join = (pathSeparator, ...parts) => {
2349
- return parts.join(pathSeparator);
2330
+ const getFeatureListVirtualDom = features => {
2331
+ return [{
2332
+ // TODO use either list or tabs role
2333
+ type: Div,
2334
+ className: FeaturesList,
2335
+ childCount: features.length,
2336
+ onClick: HandleFeaturesClick
2337
+ }, ...features.flatMap(getFeatureListItemVirtualDom)];
2350
2338
  };
2351
2339
 
2352
- const loadReadmeContent = async path => {
2353
- try {
2354
- const readmeUrl = join('/', path, 'README.md');
2355
- const readmeContent = await readFile(readmeUrl);
2356
- return readmeContent;
2357
- } catch (error) {
2358
- if (isEnoentError(error)) {
2359
- return '';
2360
- }
2361
- console.error(new VError(error, 'Failed to load Readme content'));
2362
- return `${error}`;
2340
+ const getFeaturesVirtualDom = async (features, themesHtml, selectedFeature, extension) => {
2341
+ if (features.length === 0) {
2342
+ const none$1 = none();
2343
+ return [{
2344
+ type: Div,
2345
+ className: Features,
2346
+ childCount: 3
2347
+ }, text(none$1)];
2363
2348
  }
2349
+ return [{
2350
+ type: Div,
2351
+ className: Features,
2352
+ childCount: 3
2353
+ }, ...getFeatureListVirtualDom(features), {
2354
+ type: Div,
2355
+ className: mergeClassNames(Sash, SashVertical),
2356
+ childCount: 0
2357
+ }, ...(await getFeatureContentVirtualDom(features, themesHtml, selectedFeature, extension))];
2364
2358
  };
2365
2359
 
2366
- const getSavedSelectedFeature = savedState => {
2367
- if (savedState && typeof savedState === 'object' && 'selectedFeature' in savedState && typeof savedState.selectedFeature === 'string') {
2368
- return savedState.selectedFeature;
2360
+ const getExtensionDetailContentVirtualDom = async (sanitizedReadmeHtml, themesHtml, selectedTab, features, displaySize, extensionId, extensionVersion, selectedFeature, extension, width) => {
2361
+ switch (selectedTab) {
2362
+ case Details:
2363
+ return await getDetailsVirtualDom(sanitizedReadmeHtml, displaySize, extensionId, extensionVersion, width);
2364
+ case Features$1:
2365
+ return await getFeaturesVirtualDom(features, themesHtml, selectedFeature, extension);
2366
+ case Changelog$1:
2367
+ return getChangelogVirtualDom();
2368
+ default:
2369
+ return [];
2369
2370
  }
2370
- return Details;
2371
2371
  };
2372
2372
 
2373
- const getSavedSelectedTab = savedState => {
2374
- if (savedState && typeof savedState === 'object' && 'selectedTab' in savedState && typeof savedState.selectedTab === 'string') {
2375
- return savedState.selectedTab;
2376
- }
2377
- return Details;
2373
+ const getButtonVirtualDom = (message, onClick) => {
2374
+ return [{
2375
+ type: Button,
2376
+ className: mergeClassNames(Button$1, ButtonPrimary),
2377
+ onClick,
2378
+ childCount: 1
2379
+ }, text(message)];
2378
2380
  };
2379
2381
 
2380
- const restoreState = savedState => {
2381
- const selectedTab = getSavedSelectedTab(savedState);
2382
- const selectedFeature = getSavedSelectedFeature(savedState);
2383
- return {
2384
- selectedFeature,
2385
- selectedTab
2386
- };
2382
+ const getExtensionDetailHeaderActionsVirtualDom = () => {
2383
+ const dom = [{
2384
+ type: Div,
2385
+ className: ExtensionDetailHeaderActions,
2386
+ childCount: 2
2387
+ }, ...getButtonVirtualDom('Disable', HandleClickDisable), ...getButtonVirtualDom('Uninstall', HandleClickUninstall)];
2388
+ return dom;
2387
2389
  };
2388
2390
 
2389
- const loadContent = async (state, platform, savedState) => {
2390
- const {
2391
- uri,
2392
- width,
2393
- assetDir
2394
- } = state;
2395
- const id = uri.slice('extension-detail://'.length);
2396
- const extension = await getExtension(id, platform);
2397
- const readmeContent = await loadReadmeContent(extension.path);
2398
- const baseUrl = getBaseUrl(extension.path, platform);
2399
- const readmeHtml = await renderMarkdown(readmeContent, {
2400
- baseUrl
2401
- });
2402
- const sanitizedReadmeHtml = readmeHtml;
2403
- const normalizedReadmeHtml = sanitizedReadmeHtml;
2404
- const iconSrc = getIcon(extension, platform, assetDir);
2405
- const description = getDescription(extension);
2406
- const name = getName(extension);
2407
- const size = getViewletSize(width);
2391
+ const getExtensionDetailHeaderVirtualDom = extensionDetail => {
2408
2392
  const {
2409
- selectedFeature,
2410
- selectedTab
2411
- } = restoreState(savedState);
2412
- const features = getFeatures(selectedFeature, extension);
2413
- const folderSize = await getFolderSize(extension.uri);
2414
- const entries = [{
2415
- key: 'Identifier',
2416
- value: 'abc'
2417
- }, {
2418
- key: 'Version',
2419
- value: '1.9.5'
2393
+ name,
2394
+ iconSrc,
2395
+ description
2396
+ } = extensionDetail;
2397
+ const dom = [{
2398
+ type: Div,
2399
+ className: ExtensionDetailHeader,
2400
+ childCount: 2
2420
2401
  }, {
2421
- key: 'Last Updated',
2422
- value: 'n/a'
2423
- }];
2424
- const secondEntries = [{
2425
- key: 'Published',
2426
- value: 'n/a'
2402
+ type: Img,
2403
+ className: ExtensionDetailIcon,
2404
+ alt: '',
2405
+ draggable: false,
2406
+ childCount: 0,
2407
+ src: iconSrc
2427
2408
  }, {
2428
- key: 'Last Released',
2429
- value: 'n/a'
2430
- }];
2431
- const categories = [{
2432
- id: 'themes',
2433
- label: 'Themes'
2434
- }];
2435
- const resources = [{
2436
- label: 'Marketplace',
2437
- url: '#'
2409
+ type: Div,
2410
+ className: ExtensionDetailHeaderDetails,
2411
+ childCount: 3
2438
2412
  }, {
2439
- label: 'Issues',
2440
- url: '#'
2413
+ type: Div,
2414
+ className: ExtensionDetailName,
2415
+ childCount: 1
2416
+ }, text(name), {
2417
+ type: Div,
2418
+ className: ExtensionDetailDescription,
2419
+ childCount: 1
2420
+ }, text(description), ...getExtensionDetailHeaderActionsVirtualDom()];
2421
+ return dom;
2422
+ };
2423
+
2424
+ const getTabs = selectedTab => {
2425
+ const tabs = [{
2426
+ label: 'Details',
2427
+ name: Details,
2428
+ selected: selectedTab === Details
2441
2429
  }, {
2442
- label: 'Repository',
2443
- url: '#'
2430
+ label: 'Features',
2431
+ name: Features$1,
2432
+ selected: selectedTab === Features$1
2444
2433
  }, {
2445
- label: 'License',
2446
- url: '#'
2434
+ label: 'Changelog',
2435
+ name: Changelog$1,
2436
+ selected: selectedTab === Changelog$1
2447
2437
  }];
2448
- return {
2449
- ...state,
2450
- selectedTab,
2451
- sanitizedReadmeHtml: normalizedReadmeHtml,
2452
- iconSrc,
2438
+ return tabs;
2439
+ };
2440
+
2441
+ const selectedClassName = mergeClassNames(ExtensionDetailTab, ExtensionDetailTabSelected);
2442
+ const defaultClassName = ExtensionDetailTab;
2443
+ const getTabVirtualDom = tab => {
2444
+ const {
2445
+ label,
2446
+ selected,
2447
+ name
2448
+ } = tab;
2449
+ const className = selected ? selectedClassName : defaultClassName;
2450
+ const ariaSelected = selected ? 'true' : 'false';
2451
+ return [{
2452
+ type: Button,
2453
+ role: Tab,
2453
2454
  name,
2454
- description,
2455
- sizeOnDisk: size,
2456
- entries,
2457
- secondEntries,
2458
- categories,
2459
- resources,
2460
- extension,
2461
- baseUrl,
2462
- features,
2463
- folderSize
2464
- };
2455
+ className,
2456
+ childCount: 1,
2457
+ tabIndex: -1,
2458
+ ariaSelected
2459
+ }, text(label)];
2465
2460
  };
2466
2461
 
2467
- const loadContent2 = async (state, savedState) => {
2468
- return loadContent(state, state.platform, savedState);
2462
+ const getTabsVirtualDom = tabs => {
2463
+ return [{
2464
+ type: Div,
2465
+ className: ExtensionDetailTabs,
2466
+ childCount: tabs.length,
2467
+ role: TabList,
2468
+ onClick: HandleTabsClick,
2469
+ tabIndex: 0
2470
+ }, ...tabs.flatMap(getTabVirtualDom)];
2471
+ };
2472
+
2473
+ const getClassNames = size => {
2474
+ switch (size) {
2475
+ case Small$1:
2476
+ return Small;
2477
+ case Normal$1:
2478
+ return Normal;
2479
+ case Large$1:
2480
+ return Large;
2481
+ default:
2482
+ return '';
2483
+ }
2484
+ };
2485
+
2486
+ const getExtensionDetailVirtualDom = async (extensionDetail, sanitizedReadmeHtml, selectedTab, newState) => {
2487
+ // TODO move this to view model so that rendering occurs like
2488
+ // 1. state
2489
+ // 2. view model
2490
+ // 3. virtual dom
2491
+ // 4. dom
2492
+ const themesHtml = newState?.selectedFeatureMarkdownDom || '';
2493
+ const selectedFeature = newState?.selectedFeature || '';
2494
+ const extension = newState?.extension || {};
2495
+ const features = getFeatures(selectedFeature, extension);
2496
+ const size = newState.folderSize || 0;
2497
+ const extensionId = newState?.extension?.id || 'n/a';
2498
+ const extensionVersion = newState?.extension?.version || 'n/a';
2499
+ const displaySize = getDisplaySize(size);
2500
+ const width = newState?.width || 500;
2501
+ const tabs = getTabs(selectedTab);
2502
+ const sizeValue = getViewletSize(newState?.width || 0);
2503
+ const sizeClass = getClassNames(sizeValue);
2504
+ const dom = [{
2505
+ type: Div,
2506
+ className: mergeClassNames(Viewlet, ExtensionDetail, sizeClass),
2507
+ childCount: 3
2508
+ }, ...getExtensionDetailHeaderVirtualDom(extensionDetail), ...getTabsVirtualDom(tabs), ...(await getExtensionDetailContentVirtualDom(sanitizedReadmeHtml, themesHtml, selectedTab, features, displaySize, extensionId, extensionVersion, selectedFeature, extension, width))];
2509
+ return dom;
2469
2510
  };
2470
2511
 
2471
2512
  const renderDom = async (oldState, newState) => {
@@ -2557,21 +2598,6 @@ const terminate = () => {
2557
2598
  globalThis.close();
2558
2599
  };
2559
2600
 
2560
- const wrapCommand = fn => {
2561
- const wrapped = async (uid, ...args) => {
2562
- const {
2563
- newState
2564
- } = get$1(uid);
2565
- const newerState = await fn(newState, ...args);
2566
- if (newState === newerState) {
2567
- return;
2568
- }
2569
- const latest = get$1(uid);
2570
- set$1(uid, latest.oldState, newerState);
2571
- };
2572
- return wrapped;
2573
- };
2574
-
2575
2601
  const commandMap = {
2576
2602
  'ExtensionDetail.create': create,
2577
2603
  'ExtensionDetail.diff2': diff2,
@@ -2590,10 +2616,7 @@ const commandMap = {
2590
2616
  'ExtensionDetail.resize': resize,
2591
2617
  'ExtensionDetail.saveState': saveState,
2592
2618
  'ExtensionDetail.selectTab': wrapCommand(selectTab),
2593
- 'ExtensionDetail.terminate': terminate,
2594
- // deprecated
2595
- 'ExtensionDetail.getVirtualDom2': getExtensionDetailVirtualDom2,
2596
- 'ExtensionDetail.loadContent': wrapCommand(loadContent)
2619
+ 'ExtensionDetail.terminate': terminate
2597
2620
  };
2598
2621
 
2599
2622
  const listen = async () => {