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