Compare commits
185 Commits
Author | SHA1 | Date |
---|---|---|
|
a882d0675b | |
|
5ffa213fc9 | |
|
2344353df5 | |
|
b7e063d94e | |
|
e915165065 | |
|
3de416fa3a | |
|
7dd727758c | |
|
5235155c90 | |
|
d8e4f84e5c | |
|
61014620c2 | |
|
8137985380 | |
|
8c4b4941de | |
|
b2c7d52080 | |
|
5e05834a46 | |
|
450ca5e141 | |
|
d524f88001 | |
|
12667a2b52 | |
|
52f2254d91 | |
|
10155fd1ea | |
|
bc541000c9 | |
|
b3638c04aa | |
|
bd563a8465 | |
|
485cb1158d | |
|
1a3e59e157 | |
|
b784d7f896 | |
|
b45ce81784 | |
|
808d219202 | |
|
2bda551a74 | |
|
3775e220a3 | |
|
0b41bc54da | |
|
97e8e882a3 | |
|
62fa27892c | |
|
8ce89db6e3 | |
|
0b66094876 | |
|
d4409f142a | |
|
28973ed0bc | |
|
7a983a6583 | |
|
af91ca7e16 | |
|
d4850f64fd | |
|
af77ba322a | |
|
73ac6d4efe | |
|
ffbb154c51 | |
|
90d9bead25 | |
|
a17dc8d542 | |
|
0b0314f636 | |
|
d8c453c471 | |
|
b85dbd9100 | |
|
3a01088cda | |
|
b0f6c86335 | |
|
06d592b3ba | |
|
60ea7c8551 | |
|
f9b1fbe8ab | |
|
d9020382c6 | |
|
1c5e8aaee3 | |
|
5b8254f1b2 | |
|
e12aaca02f | |
|
2775c25b5b | |
|
4ffbff6f9c | |
|
1ac6a2c97f | |
|
9aa1006f60 | |
|
b5802a5545 | |
|
fe9351f824 | |
|
8434098eaf | |
|
8dc5fd6c82 | |
|
f922f3831d | |
|
fd3fc93889 | |
|
4723c94b56 | |
|
97dd8d8f8b | |
|
aebf1d588e | |
|
1eb67b3668 | |
|
a12ce99e58 | |
|
862a33eacd | |
|
495c7997e8 | |
|
bd8f8f8d03 | |
|
cc125f24dd | |
|
250bbe68b8 | |
|
f987c05cb7 | |
|
e577cbd700 | |
|
2ae215c8c2 | |
|
901182882c | |
|
a657ae8540 | |
|
628c6f71a5 | |
|
60e5c99514 | |
|
2dc0d29598 | |
|
d330c59d41 | |
|
e0429f758d | |
|
95e60ef4a9 | |
|
9304e3a777 | |
|
c5b75d14e2 | |
|
899dca8561 | |
|
b762217ab1 | |
|
8a8003b41f | |
|
c99ae2514a | |
|
fdab31e0f7 | |
|
e449569b86 | |
|
d6c2ecad33 | |
|
ad5d51afe7 | |
|
a49ff4b891 | |
|
5a572110fe | |
|
f6dbef23f3 | |
|
461c9d6d8a | |
|
dae74b9d6d | |
|
21d8621271 | |
|
60d7704d7d | |
|
526822323c | |
|
47a6b6fb00 | |
|
17f088a0de | |
|
f6cd147f77 | |
|
1088b323ae | |
|
8c75b38bd2 | |
|
e347630c9e | |
|
674dd36513 | |
|
2ba49cc7a6 | |
|
1ab80e01c1 | |
|
c5e8289c77 | |
|
1c6df12abf | |
|
5e5110ca52 | |
|
875014bac0 | |
|
05723ec72f | |
|
68452b5dfd | |
|
53d01e8982 | |
|
0d0004ae18 | |
|
921e8a3cdf | |
|
544ba79b1d | |
|
645e77ea29 | |
|
8cd5ba7a86 | |
|
12de9ea4f8 | |
|
848670d1e6 | |
|
b03a7878f2 | |
|
c71dea1f6e | |
|
14c675ecf5 | |
|
0889f7833a | |
|
6b234663e9 | |
|
2b0075056a | |
|
fba9b1c416 | |
|
d338c0bb39 | |
|
fa34affd2d | |
|
1437c53605 | |
|
04ef289bae | |
|
90daf28206 | |
|
cb1b161a76 | |
|
8a281f8be2 | |
|
a59ccb133f | |
|
7b4580f15d | |
|
b1748d9ad8 | |
|
8316ec3a9c | |
|
84ee4cae5d | |
|
8728f7a67d | |
|
34a2801c12 | |
|
4274e2dce0 | |
|
35e442b220 | |
|
9dd4cbbce7 | |
|
f420554102 | |
|
51b07fd90f | |
|
5e8272e951 | |
|
dc9ab23e3c | |
|
944b9d4ca5 | |
|
1d976569e6 | |
|
4afbf066e1 | |
|
1311cc87b3 | |
|
b36a6dde0f | |
|
d30b8ad818 | |
|
5a4955b941 | |
|
ae5ce3d9b0 | |
|
6bd23218e7 | |
|
f0fe09fbf7 | |
|
ca1d9882af | |
|
e286ddaef9 | |
|
b4f92d7bb9 | |
|
fab5d5ab34 | |
|
3eb7b5d0f0 | |
|
5b9f12d80d | |
|
a292620f7a | |
|
7a51987a03 | |
|
282460a37a | |
|
84749ce870 | |
|
ebb53fe075 | |
|
c72a1e6148 | |
|
1d9f8c31d6 | |
|
745e5057a4 | |
|
2eeafd345d | |
|
abc521cefc | |
|
1aa6cf747e | |
|
d2719d926b | |
|
03321ab164 |
16
.babelrc.js
|
@ -1,16 +0,0 @@
|
||||||
const path = require('path')
|
|
||||||
module.exports = {
|
|
||||||
ignore: [".yarn", ".pnp.js"],
|
|
||||||
presets: ['@babel/preset-env'],
|
|
||||||
plugins: [
|
|
||||||
["@babel/plugin-transform-react-jsx", {
|
|
||||||
"runtime": "automatic"
|
|
||||||
}],
|
|
||||||
["module-resolver", {
|
|
||||||
alias: {
|
|
||||||
project: process.env.PROJECT_PATH,
|
|
||||||
dist: path.resolve(process.env.PROJECT_PATH, 'dist', process.env.PROJECT_NAME || '')
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
netlify/jsyaml.js
|
|
@ -0,0 +1,19 @@
|
||||||
|
module.exports = {
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"commonjs": true,
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-useless-escape": 0,
|
||||||
|
"no-prototype-builtins": 0,
|
||||||
|
"no-empty": 0,
|
||||||
|
"no-control-regex": 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
enableProgressBars: false
|
enableProgressBars: false
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
yarnPath: .yarn/releases/yarn-3.2.1.cjs
|
||||||
|
|
9
_headers
|
@ -2,15 +2,6 @@
|
||||||
X-Robots-Tag: all
|
X-Robots-Tag: all
|
||||||
Access-Control-Allow-Origin: *
|
Access-Control-Allow-Origin: *
|
||||||
|
|
||||||
/*.js
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/*.css
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/*.woff2
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/*.svg
|
/*.svg
|
||||||
Content-Type: image/svg+xml; charset=utf-8
|
Content-Type: image/svg+xml; charset=utf-8
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,4 @@ nvm use
|
||||||
npm install -g npm
|
npm install -g npm
|
||||||
npm install -g yarn
|
npm install -g yarn
|
||||||
yarn
|
yarn
|
||||||
yarn run babel-node tools/landscapes.js
|
yarn node tools/landscapes.js
|
||||||
|
|
|
@ -1,19 +1,30 @@
|
||||||
ip: 145.40.64.211
|
ip: 147.75.72.237
|
||||||
landscapes:
|
landscapes:
|
||||||
# name: how we name a landscape project, used on a build server for logs and settings
|
# name: how we name a landscape project, used on a build server for logs and settings
|
||||||
# repo: a github repo for a specific landscape
|
# repo: a github repo for a specific landscape
|
||||||
# netlify: full | skip - do we build it on a netlify build or not
|
# netlify: full | skip - do we build it on a netlify build or not
|
||||||
# hook: - id for a build hook, so it will be triggered after a default branch build
|
# hook: - id for a build hook, so it will be triggered after a default branch build
|
||||||
|
- landscape:
|
||||||
|
name: lfedge
|
||||||
|
repo: State-of-the-Edge/lfedge-landscape
|
||||||
|
hook: 5c80e31894c5c7758edb31e4
|
||||||
|
- landscape:
|
||||||
|
name: lfenergy
|
||||||
|
repo: lf-energy/lfenergy-landscape
|
||||||
|
hook: 606487bb9da603110d4b8139
|
||||||
|
- landscape:
|
||||||
|
name: lf
|
||||||
|
repo: jmertic/lf-landscape
|
||||||
|
hook: 606487123224e20fb8f3896e
|
||||||
|
- landscape:
|
||||||
|
name: dlt
|
||||||
|
repo: dltlandscape/dlt-landscape
|
||||||
|
hook: demo
|
||||||
- landscape:
|
- landscape:
|
||||||
name: aswf-landscape
|
name: aswf-landscape
|
||||||
repo: AcademySoftwareFoundation/aswf-landscape
|
repo: AcademySoftwareFoundation/aswf-landscape
|
||||||
hook: 608aa68eb6e5723d5d8a7e00
|
hook: 608aa68eb6e5723d5d8a7e00
|
||||||
required: true
|
required: true
|
||||||
- landscape:
|
|
||||||
name: cncf
|
|
||||||
repo: cncf/landscape
|
|
||||||
hook: 5c1bd968fdd72a78a54bdcd1
|
|
||||||
required: true
|
|
||||||
- landscape:
|
- landscape:
|
||||||
name: cdf
|
name: cdf
|
||||||
repo: cdfoundation/cdf-landscape
|
repo: cdfoundation/cdf-landscape
|
||||||
|
@ -32,23 +43,11 @@ landscapes:
|
||||||
repo: graphql/graphql-landscape
|
repo: graphql/graphql-landscape
|
||||||
hook: 5d5c7ccf64ecb5bd3d2592f7
|
hook: 5d5c7ccf64ecb5bd3d2592f7
|
||||||
required: true
|
required: true
|
||||||
- landscape:
|
|
||||||
name: lf
|
|
||||||
repo: jmertic/lf-landscape
|
|
||||||
hook: 606487123224e20fb8f3896e
|
|
||||||
- landscape:
|
- landscape:
|
||||||
name: lfai
|
name: lfai
|
||||||
repo: lfai/landscape
|
repo: lfai/landscape
|
||||||
hook: 60648e5b74c76017210a2f53
|
hook: 60648e5b74c76017210a2f53
|
||||||
required: true
|
required: true
|
||||||
- landscape:
|
|
||||||
name: lfedge
|
|
||||||
repo: State-of-the-Edge/lfedge-landscape
|
|
||||||
hook: 5c80e31894c5c7758edb31e4
|
|
||||||
- landscape:
|
|
||||||
name: lfenergy
|
|
||||||
repo: lf-energy/lfenergy-landscape
|
|
||||||
hook: 606487bb9da603110d4b8139
|
|
||||||
- landscape:
|
- landscape:
|
||||||
name: lfph
|
name: lfph
|
||||||
repo: lfph/lfph-landscape
|
repo: lfph/lfph-landscape
|
||||||
|
@ -62,10 +61,6 @@ landscapes:
|
||||||
name: openssf
|
name: openssf
|
||||||
repo: ossf/ossf-landscape
|
repo: ossf/ossf-landscape
|
||||||
hook: 613768338a16cb9182b21c3d
|
hook: 613768338a16cb9182b21c3d
|
||||||
- landscape:
|
|
||||||
name: ospo
|
|
||||||
repo: todogroup/ospolandscape
|
|
||||||
hook: 6033b43a2572da242aee6a92
|
|
||||||
- landscape:
|
- landscape:
|
||||||
name: presto
|
name: presto
|
||||||
repo: prestodb/presto-landscape
|
repo: prestodb/presto-landscape
|
||||||
|
@ -82,3 +77,7 @@ landscapes:
|
||||||
name: lfn
|
name: lfn
|
||||||
repo: lfnetworking/member_landscape
|
repo: lfnetworking/member_landscape
|
||||||
hook: 60788f5fa3cbd68181e3c209
|
hook: 60788f5fa3cbd68181e3c209
|
||||||
|
- landscape:
|
||||||
|
name: riscv
|
||||||
|
repo: riscv-admin/riscv-landscape
|
||||||
|
hook: demo
|
||||||
|
|
|
@ -9,3 +9,5 @@ server via rsync.
|
||||||
|
|
||||||
Most chances is that we will switch to a different build tool soon, so this is
|
Most chances is that we will switch to a different build tool soon, so this is
|
||||||
an experimental approach to speedup netlify builds.
|
an experimental approach to speedup netlify builds.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// We will execute this script from a landscape build,
|
// We will execute this script from a landscape build,
|
||||||
// "prepublish": "cp yarn.lock _yarn.lock",
|
// "prepublish": "cp yarn.lock _yarn.lock",
|
||||||
// "postpublish": "rm _yarn.lock || true"
|
// "postpublish": "rm _yarn.lock || true"
|
||||||
const LANDSCAPEAPP = process.env.LANDSCAPEAPP || "@latest"
|
|
||||||
const remote = `root@${process.env.BUILD_SERVER}`;
|
const remote = `root@${process.env.BUILD_SERVER}`;
|
||||||
const dockerImage = 'netlify/build:focal';
|
const dockerImage = 'netlify/build:focal';
|
||||||
const dockerHome = '/opt/buildhome';
|
const dockerHome = '/opt/buildhome';
|
||||||
|
@ -31,28 +30,20 @@ const debug = function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const runLocal = function(command, options = {}) {
|
const runLocal = function(command, showProgress) {
|
||||||
const { assignFn, showOutputFn } = options;
|
|
||||||
|
|
||||||
// report the output once every 5 seconds
|
// report the output once every 5 seconds
|
||||||
let lastOutput = { s: '', time: new Date().getTime() };
|
let lastOutput = { s: '', time: new Date().getTime() };
|
||||||
let displayIfRequired = function(text) {
|
let displayIfRequired = function(text) {
|
||||||
lastOutput.s = lastOutput.s + text;
|
if (showProgress) {
|
||||||
if (showOutputFn && showOutputFn()) {
|
console.info(text);
|
||||||
if (lastOutput.done || new Date().getTime() > lastOutput.time + 5 * 1000) {
|
|
||||||
console.info(lastOutput.s);
|
|
||||||
lastOutput.s = "";
|
|
||||||
lastOutput.time = new Date().getTime();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
lastOutput.s = lastOutput.s + text;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(function(resolve) {
|
return new Promise(function(resolve) {
|
||||||
var spawn = require('child_process').spawn;
|
var spawn = require('child_process').spawn;
|
||||||
var child = spawn('bash', ['-lc',`set -e \n${command}`]);
|
var child = spawn('bash', ['-lc',`set -e \n${command}`]);
|
||||||
if (assignFn) {
|
|
||||||
assignFn(child);
|
|
||||||
}
|
|
||||||
let output = [];
|
let output = [];
|
||||||
child.stdout.on('data', function(data) {
|
child.stdout.on('data', function(data) {
|
||||||
const text = maskSecrets(data.toString('utf-8'));
|
const text = maskSecrets(data.toString('utf-8'));
|
||||||
|
@ -93,7 +84,7 @@ ${(process.env.BUILDBOT_KEY || '').replace(/\s/g,'\n')}
|
||||||
require('fs').writeFileSync('/tmp/buildbot', key);
|
require('fs').writeFileSync('/tmp/buildbot', key);
|
||||||
require('fs').chmodSync('/tmp/buildbot', 0o600);
|
require('fs').chmodSync('/tmp/buildbot', 0o600);
|
||||||
|
|
||||||
const runRemote = async function(command, options) {
|
const runRemote = async function(command, count = 3) {
|
||||||
const bashCommand = `
|
const bashCommand = `
|
||||||
nocheck=" -o StrictHostKeyChecking=no "
|
nocheck=" -o StrictHostKeyChecking=no "
|
||||||
ssh -i /tmp/buildbot $nocheck ${remote} << 'EOSSH'
|
ssh -i /tmp/buildbot $nocheck ${remote} << 'EOSSH'
|
||||||
|
@ -101,7 +92,12 @@ const runRemote = async function(command, options) {
|
||||||
${command}
|
${command}
|
||||||
EOSSH
|
EOSSH
|
||||||
`
|
`
|
||||||
return await runLocal(bashCommand, options);
|
const result = await runLocal(bashCommand, true);
|
||||||
|
if (result.exitCode === 255 && count > 0) {
|
||||||
|
console.info(`Attempts to retry more: ${count}`);
|
||||||
|
return await runRemote(command, count - 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const runRemoteWithoutErrors = async function(command) {
|
const runRemoteWithoutErrors = async function(command) {
|
||||||
|
@ -114,16 +110,8 @@ const runRemoteWithoutErrors = async function(command) {
|
||||||
|
|
||||||
const makeRemoteBuildWithCache = async function() {
|
const makeRemoteBuildWithCache = async function() {
|
||||||
await runLocalWithoutErrors(`
|
await runLocalWithoutErrors(`
|
||||||
echo extracting
|
rm -rf packageRemote || true
|
||||||
mkdir tmpRemote
|
git clone -b deploy --single-branch https://github.com/cncf/landscapeapp packageRemote
|
||||||
cd tmpRemote
|
|
||||||
rm -rf package || true
|
|
||||||
# npm pack github:cncf/landscapeapp#vanilla
|
|
||||||
npm pack interactive-landscape${LANDSCAPEAPP}
|
|
||||||
tar xzf interactive*.tgz
|
|
||||||
cd ..
|
|
||||||
mv tmpRemote/package packageRemote
|
|
||||||
cp packageRemote/_yarn.lock packageRemote/yarn.lock
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
//how to get a hash based on our files
|
//how to get a hash based on our files
|
||||||
|
@ -156,56 +144,6 @@ const makeRemoteBuildWithCache = async function() {
|
||||||
const hash = getHash();
|
const hash = getHash();
|
||||||
const tmpHash = require('crypto').createHash('sha256').update(getTmpFile()).digest('hex');
|
const tmpHash = require('crypto').createHash('sha256').update(getTmpFile()).digest('hex');
|
||||||
// lets guarantee npm install for this folder first
|
// lets guarantee npm install for this folder first
|
||||||
{
|
|
||||||
const buildCommand = [
|
|
||||||
"(ls . ~/.nvm/nvm.sh || (curl -s -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null))",
|
|
||||||
". ~/.nvm/nvm.sh",
|
|
||||||
`nvm install ${nvmrc}`,
|
|
||||||
`echo 0`,
|
|
||||||
`nvm use ${nvmrc}`,
|
|
||||||
`echo 1`,
|
|
||||||
`npm install -g agentkeepalive --save`,
|
|
||||||
`npm install -g npm --no-progress`,
|
|
||||||
`npm install -g yarn@latest`,
|
|
||||||
`cd /opt/repo/packageRemote`,
|
|
||||||
`yarn >/dev/null`
|
|
||||||
].join(' && ');
|
|
||||||
const npmInstallCommand = `
|
|
||||||
mkdir -p /root/builds/node_cache
|
|
||||||
ls -l /root/builds/node_cache/${hash}/yarnLocal/unplugged 2>/dev/null || (
|
|
||||||
mkdir -p /root/builds/node_cache/${tmpHash}/{yarnLocal,nvm,yarnGlobal}
|
|
||||||
cp -r /root/builds/${folder}/packageRemote/.yarn/* /root/builds/node_cache/${tmpHash}/yarnLocal
|
|
||||||
chmod -R 777 /root/builds/node_cache/${tmpHash}
|
|
||||||
docker run --shm-size 1G --rm -t \
|
|
||||||
-v /root/builds/node_cache/${tmpHash}/yarnLocal:/opt/repo/packageRemote/.yarn \
|
|
||||||
-v /root/builds/node_cache/${tmpHash}/nvm:${dockerHome}/.nvm \
|
|
||||||
-v /root/builds/node_cache/${tmpHash}/yarnGlobal:${dockerHome}/.yarn \
|
|
||||||
-v /root/builds/${folder}:/opt/repo \
|
|
||||||
${dockerImage} /bin/bash -lc "${buildCommand}"
|
|
||||||
|
|
||||||
ln -s /root/builds/node_cache/${tmpHash} /root/builds/node_cache/${hash} || (
|
|
||||||
rm -rf /root/builds/node_cache/${tmpHash}
|
|
||||||
)
|
|
||||||
echo "packages for ${hash} had been installed"
|
|
||||||
)
|
|
||||||
chmod -R 777 /root/builds/node_cache/${hash}
|
|
||||||
`;
|
|
||||||
debug(npmInstallCommand);
|
|
||||||
console.info(`Remote with cache: Installing npm packages if required`);
|
|
||||||
const output = await runRemote(npmInstallCommand);
|
|
||||||
console.info(`Remote with cache: Output from npm install: exit code: ${output.exitCode}`);
|
|
||||||
if (output.exitCode !== 0) {
|
|
||||||
console.info(output.text);
|
|
||||||
throw new Error('Remote with cahce: npm install failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = output.text.split('\n');
|
|
||||||
const index = lines.indexOf(lines.filter( (line) => line.match(/added \d+ packages in/))[0]);
|
|
||||||
const filteredLines = lines.slice(index !== -1 ? index : 0).join('\n');
|
|
||||||
console.info(filteredLines || 'Reusing an existing folder for node');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// do not pass REVIEW_ID because on failure we will run it locally and report
|
// do not pass REVIEW_ID because on failure we will run it locally and report
|
||||||
// from there
|
// from there
|
||||||
const vars = [
|
const vars = [
|
||||||
|
@ -221,9 +159,14 @@ const makeRemoteBuildWithCache = async function() {
|
||||||
const outputFolder = 'landscape' + getTmpFile();
|
const outputFolder = 'landscape' + getTmpFile();
|
||||||
const buildCommand = [
|
const buildCommand = [
|
||||||
`cd /opt/repo/packageRemote`,
|
`cd /opt/repo/packageRemote`,
|
||||||
|
"(ls . ~/.nvm/nvm.sh || (curl -s -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null))",
|
||||||
`. ~/.nvm/nvm.sh`,
|
`. ~/.nvm/nvm.sh`,
|
||||||
|
`cat .nvmrc`,
|
||||||
`nvm install ${nvmrc}`,
|
`nvm install ${nvmrc}`,
|
||||||
`nvm use ${nvmrc}`,
|
`nvm use ${nvmrc}`,
|
||||||
|
`npm install -g agentkeepalive --save`,
|
||||||
|
`npm install -g npm@9 --no-progress`,
|
||||||
|
`npm install -g yarn@latest`,
|
||||||
`yarn`,
|
`yarn`,
|
||||||
`git config --global --add safe.directory /opt/repo`,
|
`git config --global --add safe.directory /opt/repo`,
|
||||||
`export NODE_OPTIONS="--unhandled-rejections=strict"`,
|
`export NODE_OPTIONS="--unhandled-rejections=strict"`,
|
||||||
|
@ -235,16 +178,12 @@ const makeRemoteBuildWithCache = async function() {
|
||||||
mkdir -p /root/builds/${outputFolder}
|
mkdir -p /root/builds/${outputFolder}
|
||||||
chmod -R 777 /root/builds/${outputFolder}
|
chmod -R 777 /root/builds/${outputFolder}
|
||||||
chmod -R 777 /root/builds/${folder}
|
chmod -R 777 /root/builds/${folder}
|
||||||
chmod -R 777 /root/builds/node_cache/${hash}
|
|
||||||
|
|
||||||
docker run --shm-size 1G --rm -t \
|
docker run --shm-size 1G --rm -t \
|
||||||
${vars.map( (v) => ` -e ${v}="${process.env[v]}" `).join(' ')} \
|
${vars.map( (v) => ` -e ${v}="${process.env[v]}" `).join(' ')} \
|
||||||
-e NVM_NO_PROGRESS=1 \
|
-e NVM_NO_PROGRESS=1 \
|
||||||
-e NETLIFY=1 \
|
-e NETLIFY=1 \
|
||||||
-e PARALLEL=TRUE \
|
-e PARALLEL=TRUE \
|
||||||
-v /root/builds/node_cache/${hash}/yarnLocal:/opt/repo/packageRemote/.yarn \
|
|
||||||
-v /root/builds/node_cache/${hash}/nvm:${dockerHome}/.nvm \
|
|
||||||
-v /root/builds/node_cache/${hash}/yarnGlobal:${dockerHome}/.yarn \
|
|
||||||
-v /root/builds/${folder}:/opt/repo \
|
-v /root/builds/${folder}:/opt/repo \
|
||||||
-v /root/builds/${outputFolder}:/dist \
|
-v /root/builds/${outputFolder}:/dist \
|
||||||
${dockerImage} /bin/bash -lc "${buildCommand}"
|
${dockerImage} /bin/bash -lc "${buildCommand}"
|
||||||
|
@ -276,7 +215,7 @@ const makeRemoteBuildWithCache = async function() {
|
||||||
rsync -az --chmod=a+r -p -e "ssh -i /tmp/buildbot -o StrictHostKeyChecking=no " ${remote}:/root/builds/${outputFolder}/dist/* distRemote
|
rsync -az --chmod=a+r -p -e "ssh -i /tmp/buildbot -o StrictHostKeyChecking=no " ${remote}:/root/builds/${outputFolder}/dist/* distRemote
|
||||||
`
|
`
|
||||||
));
|
));
|
||||||
await runRemoteWithoutErrors(
|
await runRemote(
|
||||||
`
|
`
|
||||||
rm -rf /root/builds/${folder}
|
rm -rf /root/builds/${folder}
|
||||||
rm -rf /root/builds/${outputFolder}
|
rm -rf /root/builds/${outputFolder}
|
||||||
|
@ -299,15 +238,13 @@ const makeRemoteBuildWithCache = async function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const path = require('path');
|
|
||||||
console.info('starting', process.cwd());
|
console.info('starting', process.cwd());
|
||||||
process.chdir('..');
|
process.chdir('..');
|
||||||
await runLocal('rm package*.json');
|
await runLocal('rm package*.json');
|
||||||
|
|
||||||
const cleanPromise = runRemoteWithoutErrors(`
|
const cleanPromise = runRemoteWithoutErrors(`
|
||||||
find builds/node_cache -maxdepth 1 -mtime +1 -exec rm -rf {} +;
|
|
||||||
find builds/ -maxdepth 1 -not -path "builds/node_cache" -mtime +1 -exec rm -rf {} +;
|
find builds/ -maxdepth 1 -not -path "builds/node_cache" -mtime +1 -exec rm -rf {} +;
|
||||||
`).catch(function(ex) {
|
`).catch(function() {
|
||||||
console.info('Failed to clean up a builds folder');
|
console.info('Failed to clean up a builds folder');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
|
if (process.env.KEY3) {
|
||||||
|
require('fs').mkdirSync(process.env.HOME + '/.ssh', { recursive: true});
|
||||||
|
require('fs').writeFileSync(process.env.HOME + '/.ssh/bot3',
|
||||||
|
"-----BEGIN RSA PRIVATE KEY-----\n" +
|
||||||
|
process.env.KEY3.replace(/\s/g,"\n") +
|
||||||
|
"\n-----END RSA PRIVATE KEY-----\n\n"
|
||||||
|
);
|
||||||
|
require('fs').chmodSync(process.env.HOME + '/.ssh/bot3', 0o600);
|
||||||
|
console.info('Made a bot3 file');
|
||||||
|
}
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { readdirSync, writeFileSync } = require('fs')
|
const { readdirSync, writeFileSync } = require('fs')
|
||||||
const generateIndex = require('./generateIndex')
|
const generateIndex = require('./generateIndex')
|
||||||
const run = function(x) {
|
|
||||||
console.info(require('child_process').execSync(x).toString())
|
|
||||||
}
|
|
||||||
const debug = function() {
|
const debug = function() {
|
||||||
if (process.env.DEBUG_BUILD) {
|
if (process.env.DEBUG_BUILD) {
|
||||||
console.info.apply(console, arguments);
|
console.info.apply(console, arguments);
|
||||||
|
@ -23,6 +31,7 @@ const landscapesInfo = yaml.load(require('fs').readFileSync('landscapes.yml'));
|
||||||
const dockerImage = 'netlify/build:focal';
|
const dockerImage = 'netlify/build:focal';
|
||||||
const dockerHome = '/opt/buildhome';
|
const dockerHome = '/opt/buildhome';
|
||||||
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const nvmrc = require('fs').readFileSync('.nvmrc', 'utf-8').trim();
|
const nvmrc = require('fs').readFileSync('.nvmrc', 'utf-8').trim();
|
||||||
const secrets = [
|
const secrets = [
|
||||||
|
@ -56,7 +65,7 @@ ${process.env.BUILDBOT_KEY.replace(/\s/g,'\n')}
|
||||||
|
|
||||||
// now our goal is to run this on a remote server. Step 1 - xcopy the repo
|
// now our goal is to run this on a remote server. Step 1 - xcopy the repo
|
||||||
const folder = new Date().getTime();
|
const folder = new Date().getTime();
|
||||||
const remote = 'root@147.75.76.177';
|
const remote = 'root@147.75.199.15';
|
||||||
|
|
||||||
const runRemote = async function(command) {
|
const runRemote = async function(command) {
|
||||||
const bashCommand = `
|
const bashCommand = `
|
||||||
|
@ -167,7 +176,6 @@ EOSSH
|
||||||
await runRemoteWithoutErrors(`chmod -R 777 /root/builds/${folder}`);
|
await runRemoteWithoutErrors(`chmod -R 777 /root/builds/${folder}`);
|
||||||
|
|
||||||
// lets guarantee npm install for this folder first
|
// lets guarantee npm install for this folder first
|
||||||
const branch = process.env.BRANCH;
|
|
||||||
{
|
{
|
||||||
const buildCommand = [
|
const buildCommand = [
|
||||||
"(ls . ~/.nvm/nvm.sh || (curl -s -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null))",
|
"(ls . ~/.nvm/nvm.sh || (curl -s -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash >/dev/null))",
|
||||||
|
@ -176,7 +184,8 @@ EOSSH
|
||||||
`nvm use ${nvmrc}`,
|
`nvm use ${nvmrc}`,
|
||||||
`npm install -g yarn --no-progress --silent`,
|
`npm install -g yarn --no-progress --silent`,
|
||||||
`cd /opt/repo`,
|
`cd /opt/repo`,
|
||||||
`yarn >/dev/null`
|
`yarn >/dev/null`,
|
||||||
|
`yarn eslint`
|
||||||
].join(' && ');
|
].join(' && ');
|
||||||
const npmInstallCommand = `
|
const npmInstallCommand = `
|
||||||
mkdir -p /root/builds/${folder}_node
|
mkdir -p /root/builds/${folder}_node
|
||||||
|
@ -207,6 +216,7 @@ EOSSH
|
||||||
const outputFolder = landscape.name + new Date().getTime();
|
const outputFolder = landscape.name + new Date().getTime();
|
||||||
const buildCommand = [
|
const buildCommand = [
|
||||||
`cd /opt/repo`,
|
`cd /opt/repo`,
|
||||||
|
`git config --global --add safe.directory /opt/repo`,
|
||||||
`. ~/.nvm/nvm.sh`,
|
`. ~/.nvm/nvm.sh`,
|
||||||
`nvm use`,
|
`nvm use`,
|
||||||
`export NODE_OPTIONS="--unhandled-rejections=strict"`,
|
`export NODE_OPTIONS="--unhandled-rejections=strict"`,
|
||||||
|
@ -314,24 +324,18 @@ EOSSH
|
||||||
await runLocalWithoutErrors('cp -r dist netlify');
|
await runLocalWithoutErrors('cp -r dist netlify');
|
||||||
|
|
||||||
if (process.env.BRANCH === 'master') {
|
if (process.env.BRANCH === 'master') {
|
||||||
|
console.info(await runLocal('git remote -v'));
|
||||||
await runLocalWithoutErrors(`
|
await runLocalWithoutErrors(`
|
||||||
git config --global user.email "info@cncf.io"
|
git config --global user.email "info@cncf.io"
|
||||||
git config --global user.name "CNCF-bot"
|
git config --global user.name "CNCF-bot"
|
||||||
git remote rm github 2>/dev/null || true
|
git remote rm github 2>/dev/null || true
|
||||||
git remote add github "https://$GITHUB_USER:$GITHUB_TOKEN@github.com/cncf/landscapeapp"
|
git remote add github "git@github.com:cncf/landscapeapp.git"
|
||||||
git fetch github
|
echo 1
|
||||||
# git diff # Need to comment this when a diff is too large
|
GIT_SSH_COMMAND='ssh -i ~/.ssh/bot3 -o IdentitiesOnly=yes' git fetch github
|
||||||
git checkout -- .
|
echo 2
|
||||||
npm version patch || npm version patch || npm version patch
|
git --no-pager show HEAD
|
||||||
git commit -m 'Update to a new version [skip ci]' --allow-empty --amend
|
echo 3
|
||||||
git branch -D tmp || true
|
GIT_SSH_COMMAND='ssh -i ~/.ssh/bot3 -o IdentitiesOnly=yes' git push github github/master:deploy
|
||||||
git checkout -b tmp
|
|
||||||
git push github HEAD:master || true
|
|
||||||
git push github HEAD:master --tags --force
|
|
||||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
|
||||||
git diff
|
|
||||||
npm -q publish || (sleep 5 && npm -q publish) || (sleep 30 && npm -q publish)
|
|
||||||
echo 'Npm package published'
|
|
||||||
`);
|
`);
|
||||||
// just for debug purpose
|
// just for debug purpose
|
||||||
//now we have a different hash, because we updated a version, but for build purposes we have exactly same npm modules
|
//now we have a different hash, because we updated a version, but for build purposes we have exactly same npm modules
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
// this "server.js" script should be able to run everything itself, without
|
||||||
|
// having to bother with any packages or similar problems.
|
||||||
|
const fs = require('fs/promises');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const oldCreateConnection = require('https').globalAgent.createConnection;
|
||||||
|
require('https').globalAgent.createConnection = function(options, cb) {
|
||||||
|
options.highWaterMark = 1024 * 1024;
|
||||||
|
options.readableHighWaterMark = 1024 * 1024;
|
||||||
|
return oldCreateConnection.apply(this, [options, cb]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// we will get a content of all files, in a form of entries
|
||||||
|
// "file", "content", "md5"
|
||||||
|
async function getContent() {
|
||||||
|
const dirs = ["images", "hosted_logos", "cached_logos"];
|
||||||
|
const files = ["landscape.yml", "settings.yml", "processed_landscape.yml", "guide.md"];
|
||||||
|
const all = [];
|
||||||
|
for (let dir of dirs) {
|
||||||
|
const filesInDir = await fs.readdir(dir);
|
||||||
|
for (let file of filesInDir) {
|
||||||
|
if (file !== '.' && file !== '..') {
|
||||||
|
const content = await fs.readFile(`${dir}/${file}`, { encoding: 'base64'});
|
||||||
|
const md5 = require('crypto').createHash('md5').update(content).digest("hex");
|
||||||
|
all.push({
|
||||||
|
file: `${dir}/${file}`,
|
||||||
|
content: content,
|
||||||
|
md5: md5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let file of files) {
|
||||||
|
let content;
|
||||||
|
try {
|
||||||
|
content = await fs.readFile(file, { encoding: 'base64'});
|
||||||
|
} catch(ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (content) {
|
||||||
|
const md5 = require('crypto').createHash('md5').update(content).digest("hex");
|
||||||
|
all.push({
|
||||||
|
file: file,
|
||||||
|
content: content,
|
||||||
|
md5: md5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(path) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
const base = process.env.DEBUG_SERVER ? 'http://localhost:3000' : 'https://weblandscapes.ddns.net';
|
||||||
|
const http = require(base.indexOf('http://') === 0 ? 'http' : 'https');
|
||||||
|
const originalPath = path;
|
||||||
|
path = `${base}/api/console/download/${path}`;
|
||||||
|
const req = http.request(path, function(res) {
|
||||||
|
const path1 = originalPath.replace('?', '.html?');
|
||||||
|
if (res.statusCode === 404 && path.indexOf('.html') === -1) {
|
||||||
|
get(path1).then( (x) => resolve(x));
|
||||||
|
} else {
|
||||||
|
resolve({
|
||||||
|
res: res,
|
||||||
|
headers: res.headers,
|
||||||
|
statusCode: res.statusCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function post({path, request}) {
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
const base = process.env.DEBUG_SERVER ? 'http://localhost:3000' : 'https://weblandscapes.ddns.net';
|
||||||
|
const http = require(base.indexOf('http://') === 0 ? 'http' : 'https');
|
||||||
|
|
||||||
|
let data = '';
|
||||||
|
const req = http.request({
|
||||||
|
hostname: base.split('://')[1].replace(':3000', ''),
|
||||||
|
port: base.indexOf('3000') !== -1 ? '3000' : 443,
|
||||||
|
path: path,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8'
|
||||||
|
}
|
||||||
|
}, function(res) {
|
||||||
|
res.on('data', function(chunk) {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
res.on('close', function() {
|
||||||
|
resolve(JSON.parse(data));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.write(JSON.stringify(request));
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// a build is done on a server side, instead of a client side, this
|
||||||
|
// is a key moment
|
||||||
|
// 1. get content (that is fast)
|
||||||
|
// 2. send a list of hashes
|
||||||
|
// 3. get back a list of existing hashes
|
||||||
|
// 4. send a list of (file, content, hash) , but skip the content when a hash is on the server
|
||||||
|
// 5. get a build result on a server
|
||||||
|
let previousGlobalHash;
|
||||||
|
let lastOutput;
|
||||||
|
let currentPath;
|
||||||
|
async function build() {
|
||||||
|
const files = await getContent();
|
||||||
|
if (!previousGlobalHash) {
|
||||||
|
console.info(`Starting a new build...`);
|
||||||
|
}
|
||||||
|
if (JSON.stringify(files.map( (x) => x.md5)) === previousGlobalHash) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (previousGlobalHash) {
|
||||||
|
console.info(`Changes detected, starting a new build`);
|
||||||
|
}
|
||||||
|
previousGlobalHash = JSON.stringify(files.map( (x) => x.md5));
|
||||||
|
const availableIds = await post({path: '/api/console/ids', request: { ids: files.map( (x) => x.md5 ) }});
|
||||||
|
const availableSet = new Set(availableIds.existingIds);
|
||||||
|
const filesWithoutExtraContent = files.map( (file) => ({
|
||||||
|
file: file.file,
|
||||||
|
md5: file.md5,
|
||||||
|
content: availableSet.has(file.md5) ? '' : file.content
|
||||||
|
}));
|
||||||
|
const result = await post({path: '/api/console/preview', request: { files: filesWithoutExtraContent }});
|
||||||
|
if (result.success) {
|
||||||
|
currentPath = result.path;
|
||||||
|
console.info(`${new Date().toISOString()} build result: ${result.success ? 'success' : 'failure'} `);
|
||||||
|
} else {
|
||||||
|
lastOutput = result.output;
|
||||||
|
console.info(`${new Date().toISOString()} build result: ${result.success ? 'success' : 'failure'} `);
|
||||||
|
console.info(result.output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function server() {
|
||||||
|
const http = require('http');
|
||||||
|
http.createServer(async function (request, response) {
|
||||||
|
if (!currentPath) {
|
||||||
|
response.writeHead(404);
|
||||||
|
if (lastOutput) {
|
||||||
|
response.end(lastOutput);
|
||||||
|
} else {
|
||||||
|
response.end('Site is not ready');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let filePath = request.url.split('?')[0];
|
||||||
|
const url = path.join(currentPath, 'dist', filePath + '?' + request.url.split('?')[1]);
|
||||||
|
console.info(`Fetching ${url}`);
|
||||||
|
const output = await get(url);
|
||||||
|
response.writeHead(output.statusCode, output.headers);
|
||||||
|
output.res.pipe(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).listen(process.env.PORT || 8001);
|
||||||
|
console.log(`Development server running at http://127.0.0.1:${process.env.PORT || 8001}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
server();
|
||||||
|
//eslint-disable-next-line no-constant-condition
|
||||||
|
while(true) {
|
||||||
|
await build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main().catch(function(ex) {
|
||||||
|
console.info(ex);
|
||||||
|
});
|
||||||
|
// how will a server work?
|
74
package.json
|
@ -1,41 +1,42 @@
|
||||||
{
|
{
|
||||||
"name": "interactive-landscape",
|
"name": "interactive-landscape",
|
||||||
"version": "1.0.651",
|
"version": "1.0.657",
|
||||||
"description": "Visualization tool for building interactive landscapes",
|
"description": "Visualization tool for building interactive landscapes",
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">=3",
|
"npm": ">=3",
|
||||||
"node": ">= 10.5"
|
"node": ">= 10.5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"autocrop-images": "babel-node tools/autocropImages",
|
"eslint": "eslint src tools specs netlify",
|
||||||
|
"autocrop-images": "node tools/autocropImages",
|
||||||
"dev": "yarn server",
|
"dev": "yarn server",
|
||||||
"open:src": "yarn server",
|
"open:src": "yarn server",
|
||||||
"server": "babel-node server.js",
|
"server": "node server.js",
|
||||||
"landscapes": "babel-node tools/landscapes.js",
|
"landscapes": "node tools/landscapes.js",
|
||||||
"update-github-colors": "curl https://raw.githubusercontent.com/Diastro/github-colors/master/github-colors.json > tools/githubColors.json",
|
"update-github-colors": "curl https://raw.githubusercontent.com/Diastro/github-colors/master/github-colors.json > tools/githubColors.json",
|
||||||
"fetch": "babel-node tools/validateLandscape && babel-node tools/checkWrongCharactersInFilenames && babel-node tools/addExternalInfo.js && yarn yaml2json",
|
"fetch": "node tools/validateLandscape && node tools/checkWrongCharactersInFilenames && node tools/addExternalInfo.js && yarn yaml2json",
|
||||||
"fetchAll": "LEVEL=complete yarn fetch",
|
"fetchAll": "LEVEL=complete yarn fetch",
|
||||||
"update": "(rm /tmp/landscape.json || true) && babel-node tools/validateLandscape && yarn remove-quotes && LEVEL=medium babel-node tools/addExternalInfo.js && yarn prune && yarn check-links && yarn yaml2json && babel-node tools/calculateNumberOfTweets && babel-node tools/updateTimestamps",
|
"light-update": "(rm /tmp/landscape.json || true) && node tools/validateLandscape && yarn remove-quotes && LEVEL=crunchbase node tools/addExternalInfo.js && yarn prune && yarn yaml2json && node tools/updateTimestamps",
|
||||||
"yaml2json": "babel-node tools/generateJson.js",
|
"update": "(rm /tmp/landscape.json || true) && node tools/validateLandscape && yarn remove-quotes && LEVEL=medium node tools/addExternalInfo.js && yarn prune && yarn check-links && yarn yaml2json && node tools/updateTimestamps",
|
||||||
"remove-quotes": "babel-node tools/removeQuotes",
|
"yaml2json": "node tools/generateJson.js",
|
||||||
"prune": "babel-node tools/pruneExtraEntries",
|
"remove-quotes": "node tools/removeQuotes",
|
||||||
"check-links": "babel-node tools/checkLinks",
|
"prune": "node tools/pruneExtraEntries",
|
||||||
"remove-dist": "rimraf \"$PROJECT_PATH\"/dist",
|
"check-links": "node tools/checkLinks",
|
||||||
"precommit": "yarn fetch",
|
"precommit": "yarn fetch",
|
||||||
"start-ci": "yarn exec bash -c \"(yarn run babel-node tools/distServer.js &) && sleep 10\"",
|
"start-ci": "yarn exec bash -c \"(node tools/distServer.js &) && sleep 10\"",
|
||||||
"stop-old-ci": "yarn run babel-node tools/stopOldDistServer.js",
|
"stop-old-ci": "node tools/stopOldDistServer.js",
|
||||||
"stop-ci": "yarn exec bash -c \"kill -9 `cat /tmp/ci.pid` >/dev/null 2>/dev/null && rm /tmp/ci.pid \"",
|
"stop-ci": "yarn exec bash -c \"kill -9 `cat /tmp/ci.pid` >/dev/null 2>/dev/null && rm /tmp/ci.pid \"",
|
||||||
"integration-test": "jest --runInBand --reporters=jest-standard-reporter",
|
"integration-test": "jest --runInBand --reporters=jest-standard-reporter",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"check-landscape": "babel-node tools/checkLandscape",
|
"check-landscape": "node tools/checkLandscape",
|
||||||
"render-landscape": "babel-node tools/renderLandscape",
|
"render-landscape": "node tools/renderLandscape",
|
||||||
"funding": "babel-node tools/fundingForMasterBranch",
|
"funding": "node tools/fundingForMasterBranch",
|
||||||
"prepare-landscape": "babel-node tools/prepareLandscape.js && babel-node tools/renderItems.js",
|
"prepare-landscape": "node tools/prepareLandscape.js && node tools/renderItems.js",
|
||||||
"setup-robots": "babel-node tools/sitemap && babel-node tools/addRobots",
|
"setup-robots": "node tools/sitemap && node tools/addRobots",
|
||||||
"build": "yarn fetch && yarn prepare-landscape && babel-node tools/renderAcquisitions && yarn setup-robots && yarn export-functions && yarn stop-old-ci && yarn start-ci && babel-node tools/parallelWithRetry integration-test check-landscape render-landscape funding && yarn stop-ci",
|
"build": "yarn fetch && yarn prepare-landscape && node tools/renderAcquisitions && yarn setup-robots && yarn export-functions && yarn stop-old-ci && yarn start-ci && node tools/parallelWithRetry integration-test check-landscape render-landscape funding && yarn stop-ci",
|
||||||
"export-functions": "babel-node ./tools/exportFunctions",
|
"export-functions": "node ./tools/exportFunctions",
|
||||||
"latest": "yarn",
|
"latest": "yarn",
|
||||||
"reset-tweet-count": "babel-node tools/resetTweetCount.js",
|
"reset-tweet-count": "node tools/resetTweetCount.js",
|
||||||
"prepublish": "cp yarn.lock _yarn.lock",
|
"prepublish": "cp yarn.lock _yarn.lock",
|
||||||
"postpublish": "rm _yarn.lock || true",
|
"postpublish": "rm _yarn.lock || true",
|
||||||
"preview": "yarn fetch && yarn prepare-landscape && yarn export-functions"
|
"preview": "yarn fetch && yarn prepare-landscape && yarn export-functions"
|
||||||
|
@ -43,26 +44,15 @@
|
||||||
"author": "CNCF",
|
"author": "CNCF",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.17.10",
|
"@vercel/ncc": "^0.34.0",
|
||||||
"@babel/node": "^7.17.10",
|
|
||||||
"@babel/plugin-transform-react-jsx": "^7.17.3",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.17.10",
|
|
||||||
"@babel/preset-env": "^7.17.10",
|
|
||||||
"@babel/register": "^7.17.7",
|
|
||||||
"@babel/runtime": "^7.17.9",
|
|
||||||
"@vercel/ncc": "^0.33.4",
|
|
||||||
"anchorme": "^2.1.2",
|
"anchorme": "^2.1.2",
|
||||||
"axe-puppeteer": "^1.1.1",
|
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"babel-plugin-module-resolver": "^4.1.0",
|
|
||||||
"bluebird": "3.7.2",
|
"bluebird": "3.7.2",
|
||||||
"change-case": "^4.1.2",
|
"change-case": "^4.1.2",
|
||||||
"cheerio": "^1.0.0-rc.10",
|
"cheerio": "^1.0.0-rc.11",
|
||||||
"chokidar": "^3.5.3",
|
|
||||||
"classnames": "^2.3.1",
|
|
||||||
"colors": "1.4.0",
|
"colors": "1.4.0",
|
||||||
"compression": "^1.7.4",
|
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
|
"eslint": "latest",
|
||||||
"event-emitter": "0.3.5",
|
"event-emitter": "0.3.5",
|
||||||
"expect-puppeteer": "^6.1.0",
|
"expect-puppeteer": "^6.1.0",
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
|
@ -78,13 +68,8 @@
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-emoji": "^1.11.0",
|
"node-emoji": "^1.11.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"open": "^8.4.0",
|
"puppeteer": "^14.2.1",
|
||||||
"puppeteer": "^13.7.0",
|
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"raf": "3.4.1",
|
|
||||||
"react": "^18.1.0",
|
|
||||||
"react-dom": "^18.1.0",
|
|
||||||
"regenerator-runtime": "^0.13.9",
|
|
||||||
"relative-date": "1.1.3",
|
"relative-date": "1.1.3",
|
||||||
"sanitize-html": "^2.7.0",
|
"sanitize-html": "^2.7.0",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
|
@ -108,10 +93,5 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/cncf/landscapeapp"
|
"url": "https://github.com/cncf/landscapeapp"
|
||||||
},
|
},
|
||||||
"dependenciesMeta": {
|
"packageManager": "yarn@3.2.1"
|
||||||
"open": {
|
|
||||||
"unplugged": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@3.2.0"
|
|
||||||
}
|
}
|
||||||
|
|
12
server.js
|
@ -7,6 +7,12 @@ const http = require('http');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const fnFile = (file) => {
|
||||||
|
const functionsPath = path.join(projectPath, 'dist', process.env.PROJECT_NAME || '', 'functions' );
|
||||||
|
const destFile = [process.env.PROJECT_NAME, file].filter(_ => _).join('--');
|
||||||
|
return path.join(functionsPath, destFile);
|
||||||
|
}
|
||||||
|
|
||||||
http.createServer(function (request, response) {
|
http.createServer(function (request, response) {
|
||||||
|
|
||||||
if (request.url.indexOf('/api/ids') !== -1) {
|
if (request.url.indexOf('/api/ids') !== -1) {
|
||||||
|
@ -14,7 +20,8 @@ http.createServer(function (request, response) {
|
||||||
const query = request.url.split('?')[1] || '';
|
const query = request.url.split('?')[1] || '';
|
||||||
|
|
||||||
if (!process.env.INLINE_API) {
|
if (!process.env.INLINE_API) {
|
||||||
require('child_process').exec(`babel-node src/api/ids.js '${query}'`, {}, function(e, output, err) {
|
require('child_process').exec(`node ${fnFile("ids.js")} '${query}'`, {}, function(e, output, err) {
|
||||||
|
console.info(err);
|
||||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
response.end(output);
|
response.end(output);
|
||||||
});
|
});
|
||||||
|
@ -30,7 +37,7 @@ http.createServer(function (request, response) {
|
||||||
const query = request.url.split('?')[1] || '';
|
const query = request.url.split('?')[1] || '';
|
||||||
|
|
||||||
if (!process.env.INLINE_API) {
|
if (!process.env.INLINE_API) {
|
||||||
require('child_process').exec(`babel-node src/api/export.js '${query}'`, {}, function(e, output, err) {
|
require('child_process').exec(`node ${fnFile("export.js")} '${query}'`, {}, function(e, output, err) {
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
'Content-Type': 'text/css',
|
'Content-Type': 'text/css',
|
||||||
'Content-Disposition': 'attachment; filename=interactive-landscape.csv'
|
'Content-Disposition': 'attachment; filename=interactive-landscape.csv'
|
||||||
|
@ -47,6 +54,7 @@ http.createServer(function (request, response) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let filePath = path.join(process.env.PROJECT_PATH, 'dist', request.url.split('?')[0]);
|
let filePath = path.join(process.env.PROJECT_PATH, 'dist', request.url.split('?')[0]);
|
||||||
if (fs.existsSync(path.resolve(filePath, 'index.html'))) {
|
if (fs.existsSync(path.resolve(filePath, 'index.html'))) {
|
||||||
filePath = path.resolve(filePath, 'index.html');
|
filePath = path.resolve(filePath, 'index.html');
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
jest: true,
|
||||||
|
test: true,
|
||||||
|
it: true,
|
||||||
|
describe: true,
|
||||||
|
expect: true
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,13 +1,11 @@
|
||||||
import 'regenerator-runtime/runtime';
|
const puppeteer = require("puppeteer");
|
||||||
import puppeteer from "puppeteer";
|
require('expect-puppeteer');
|
||||||
import 'expect-puppeteer';
|
const { paramCase } = require('change-case');
|
||||||
import { paramCase } from 'change-case';
|
const { settings } = require('../tools/settings');
|
||||||
import { settings } from '../tools/settings';
|
const { projects } = require('../tools/loadData');
|
||||||
import { projects } from '../tools/loadData';
|
const { landscapeSettingsList } = require("../src/utils/landscapeSettings");
|
||||||
import { landscapeSettingsList } from "../src/utils/landscapeSettings";
|
const { appUrl, pathPrefix } = require('../tools/distSettings');
|
||||||
import { appUrl, pathPrefix } from '../tools/distSettings'
|
|
||||||
|
|
||||||
const devicesMap = puppeteer.devices;
|
|
||||||
const width = 1920;
|
const width = 1920;
|
||||||
const height = 1080;
|
const height = 1080;
|
||||||
|
|
||||||
|
@ -40,7 +38,7 @@ async function makePage(initialUrl) {
|
||||||
console.info('retrying...', ex);
|
console.info('retrying...', ex);
|
||||||
browser.close();
|
browser.close();
|
||||||
} catch(ex2) {
|
} catch(ex2) {
|
||||||
|
console.info('failed to close browser', ex2);
|
||||||
}
|
}
|
||||||
return await makePage(initialUrl);
|
return await makePage(initialUrl);
|
||||||
}
|
}
|
||||||
|
@ -58,29 +56,29 @@ async function waitForHeaderText(page, text) {
|
||||||
await page.waitForFunction(`[...document.querySelectorAll('.sh_wrapper')].find( (x) => x.innerText.includes('${text}'))`);
|
await page.waitForFunction(`[...document.querySelectorAll('.sh_wrapper')].find( (x) => x.innerText.includes('${text}'))`);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("Embed test", () => {
|
// describe("Embed test", () => {
|
||||||
describe("I visit an example embed page", () => {
|
// describe("I visit an example embed page", () => {
|
||||||
let frame;
|
// let frame;
|
||||||
test('page is open and has a frame', async function(){
|
// test('page is open and has a frame', async function(){
|
||||||
page = await makePage(appUrl + '/embed');
|
// page = await makePage(appUrl + '/embed');
|
||||||
frame = await page.frames()[1];
|
// frame = await page.frames()[1];
|
||||||
await frame.waitForSelector('.cards-section .mosaic');
|
// await frame.waitForSelector('.cards-section .mosaic');
|
||||||
await frame.waitForXPath(`//h1[contains(text(), 'full interactive landscape')]`);
|
// await waitForSelector(frame, '#embedded-footer');
|
||||||
});
|
// });
|
||||||
|
|
||||||
test('Do not see a content from a main mode', async function() {
|
// test('Do not see a content from a main mode', async function() {
|
||||||
const title = await frame.$('h1', { text: settings.test.header })
|
// const title = await frame.$('h1', { text: settings.test.header })
|
||||||
expect(await title.boundingBox()).toBe(null)
|
// expect(await title.boundingBox()).toBe(null)
|
||||||
});
|
// });
|
||||||
|
|
||||||
// ensure that it is clickable
|
// // ensure that it is clickable
|
||||||
test('I can click on a tile in a frame and I get a modal after that', async function() {
|
// test('I can click on a tile in a frame and I get a modal after that', async function() {
|
||||||
await waitForSelector(frame, ".cards-section .mosaic img");
|
// await waitForSelector(frame, ".cards-section .mosaic img");
|
||||||
await frame.click(`.mosaic img`);
|
// await frame.click(`.mosaic img`);
|
||||||
});
|
// });
|
||||||
close();
|
// close();
|
||||||
}, 6 * 60 * 1000); //give it up to 1 min to execute
|
// }, 6 * 60 * 1000); //give it up to 1 min to execute
|
||||||
});
|
// });
|
||||||
|
|
||||||
describe("Main test", () => {
|
describe("Main test", () => {
|
||||||
describe("I visit a main page and have all required elements", () => {
|
describe("I visit a main page and have all required elements", () => {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'regenerator-runtime/runtime';
|
const { actualTwitter } = require('../../tools/actualTwitter');
|
||||||
import actualTwitter from '../../tools/actualTwitter';
|
|
||||||
|
|
||||||
describe('Twitter URL', () => {
|
describe('Twitter URL', () => {
|
||||||
describe('when crunchbase data not set', () => {
|
describe('when crunchbase data not set', () => {
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import path from 'path';
|
const { flattenItems } = require('../utils/itemsCalculator');
|
||||||
import fs from 'fs';
|
const { getGroupedItems, expandSecondPathItems } = require('../utils/itemsCalculator');
|
||||||
|
const { parseParams } = require('../utils/routing');
|
||||||
|
const Parser = require('json2csv/lib/JSON2CSVParser');
|
||||||
|
const { readJsonFromDist } = require('../utils/readJson');
|
||||||
|
|
||||||
import allItems from 'dist/data/items-export';
|
const allItems = readJsonFromDist('data/items-export');
|
||||||
import projects from 'dist/data/items';
|
const projects = readJsonFromDist('data/items');
|
||||||
import settings from 'dist/settings'
|
|
||||||
|
|
||||||
import { flattenItems, expandSecondPathItems } from '../utils/itemsCalculator';
|
const processRequest = module.exports.processRequest = query => {
|
||||||
import getGroupedItems from '../utils/itemsCalculator';
|
|
||||||
import getSummary, { getSummaryText } from '../utils/summaryCalculator';
|
|
||||||
import { parseParams } from '../utils/routing';
|
|
||||||
import Parser from 'json2csv/lib/JSON2CSVParser';
|
|
||||||
|
|
||||||
export const processRequest = query => {
|
|
||||||
const params = parseParams(query);
|
const params = parseParams(query);
|
||||||
const p = new URLSearchParams(query);
|
const p = new URLSearchParams(query);
|
||||||
params.format = p.get('format');
|
params.format = p.get('format');
|
||||||
|
@ -19,16 +15,16 @@ export const processRequest = query => {
|
||||||
let items = projects;
|
let items = projects;
|
||||||
if (params.grouping === 'landscape' || params.format !== 'card') {
|
if (params.grouping === 'landscape' || params.format !== 'card') {
|
||||||
items = expandSecondPathItems(items);
|
items = expandSecondPathItems(items);
|
||||||
};
|
}
|
||||||
|
|
||||||
// extract alias - if grouping = category
|
// extract alias - if grouping = category
|
||||||
// extract alias - if params != card-mode (big_picture - always show)
|
// extract alias - if params != card-mode (big_picture - always show)
|
||||||
// i.e. make a copy to items here - to get a list of ids
|
// i.e. make a copy to items here - to get a list of ids
|
||||||
|
|
||||||
const selectedItems = flattenItems(getGroupedItems({data: items, ...params}))
|
const selectedItems = flattenItems(getGroupedItems({data: items, skipDuplicates: params.format === 'card', ...params}))
|
||||||
.reduce((acc, item) => ({ ...acc, [item.id]: true }), {})
|
.reduce((acc, item) => ({ ...acc, [item.id]: true }), {})
|
||||||
|
|
||||||
const fields = allItems[0].map(([label, _]) => label !== 'id' && label).filter(_ => _);
|
const fields = allItems[0].map(([label]) => label !== 'id' && label).filter(_ => _);
|
||||||
const itemsForExport = allItems
|
const itemsForExport = allItems
|
||||||
.map(item => item.reduce((acc, [label, value]) => ({ ...acc, [label]: value }), {}))
|
.map(item => item.reduce((acc, [label, value]) => ({ ...acc, [label]: value }), {}))
|
||||||
.filter(item => selectedItems[item.id]);
|
.filter(item => selectedItems[item.id]);
|
||||||
|
@ -39,7 +35,7 @@ export const processRequest = query => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netlify function
|
// Netlify function
|
||||||
export async function handler(event, context) {
|
module.exports.handler = async function(event) {
|
||||||
const body = processRequest(event.queryStringParameters)
|
const body = processRequest(event.queryStringParameters)
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': 'text/css',
|
'Content-Type': 'text/css',
|
||||||
|
@ -48,6 +44,6 @@ export async function handler(event, context) {
|
||||||
return { statusCode: 200, body: body, headers }
|
return { statusCode: 200, body: body, headers }
|
||||||
}
|
}
|
||||||
if (__filename === process.argv[1]) {
|
if (__filename === process.argv[1]) {
|
||||||
console.info(JSON.stringify(processRequest(process.argv[2]), null, 4));
|
console.info(processRequest(process.argv[2]), null, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import path from 'path';
|
const { expandSecondPathItems } = require('../utils/itemsCalculator');
|
||||||
import fs from 'fs';
|
const { getGroupedItems } = require('../utils/itemsCalculator');
|
||||||
import projects from 'dist/data/items';
|
const { getSummary, getSummaryText } = require('../utils/summaryCalculator');
|
||||||
import settings from 'dist/settings'
|
const { parseParams } = require('../utils/routing');
|
||||||
|
const { readJsonFromDist } = require('../utils/readJson');
|
||||||
|
|
||||||
import { flattenItems, expandSecondPathItems } from '../utils/itemsCalculator'
|
const projects = readJsonFromDist('data/items');
|
||||||
import getGroupedItems from '../utils/itemsCalculator'
|
|
||||||
import getSummary, { getSummaryText } from '../utils/summaryCalculator';
|
|
||||||
import { parseParams } from '../utils/routing'
|
|
||||||
|
|
||||||
export const processRequest = query => {
|
const processRequest = module.exports.processRequest = query => {
|
||||||
const params = parseParams(query);
|
const params = parseParams(query);
|
||||||
const p = new URLSearchParams(query);
|
const p = new URLSearchParams(query);
|
||||||
params.format = p.get('format');
|
params.format = p.get('format');
|
||||||
|
@ -16,10 +14,10 @@ export const processRequest = query => {
|
||||||
let items = projects;
|
let items = projects;
|
||||||
if (params.grouping === 'landscape' || params.format !== 'card') {
|
if (params.grouping === 'landscape' || params.format !== 'card') {
|
||||||
items = expandSecondPathItems(items);
|
items = expandSecondPathItems(items);
|
||||||
};
|
}
|
||||||
|
|
||||||
const summary = getSummary({data: items, ...params});
|
const summary = getSummary({data: items, ...params});
|
||||||
const groupedItems = getGroupedItems({data: items, ...params})
|
const groupedItems = getGroupedItems({data: items, skipDuplicates: params.format === 'card', ...params })
|
||||||
.map(group => {
|
.map(group => {
|
||||||
const items = group.items.map(({ id }) => ({ id } ))
|
const items = group.items.map(({ id }) => ({ id } ))
|
||||||
return { ...group, items }
|
return { ...group, items }
|
||||||
|
@ -32,7 +30,7 @@ export const processRequest = query => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netlify function
|
// Netlify function
|
||||||
export async function handler(event, context) {
|
module.exports.handler = async function(event) {
|
||||||
const body = processRequest(event.queryStringParameters)
|
const body = processRequest(event.queryStringParameters)
|
||||||
const headers = { 'Content-Type': 'application/json' }
|
const headers = { 'Content-Type': 'application/json' }
|
||||||
return { statusCode: 200, body: JSON.stringify(body), headers }
|
return { statusCode: 200, body: JSON.stringify(body), headers }
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import path from 'path';
|
const { flattenItems, expandSecondPathItems } = require('../utils/itemsCalculator');
|
||||||
import fs from 'fs';
|
const { getGroupedItems } = require('../utils/itemsCalculator');
|
||||||
import projects from 'dist/data/items';
|
const { parseParams } = require('../utils/routing');
|
||||||
import settings from 'dist/settings'
|
const { readJsonFromDist } = require('../utils/readJson');
|
||||||
|
|
||||||
import { flattenItems, expandSecondPathItems } from '../utils/itemsCalculator'
|
const projects = readJsonFromDist('data/items');
|
||||||
import getGroupedItems from '../utils/itemsCalculator'
|
const settings = readJsonFromDist('settings');
|
||||||
import { parseParams } from '../utils/routing'
|
|
||||||
|
|
||||||
export const processRequest = query => {
|
const processRequest = module.exports.processRequest = query => {
|
||||||
const params = parseParams({ mainContentMode: 'card-mode', ...query })
|
const params = parseParams({ mainContentMode: 'card-mode', ...query })
|
||||||
// extract alias - if grouping = category
|
// extract alias - if grouping = category
|
||||||
// extract alias - if params != card-mode (big_picture - always show)
|
// extract alias - if params != card-mode (big_picture - always show)
|
||||||
|
@ -17,17 +16,20 @@ export const processRequest = query => {
|
||||||
items = expandSecondPathItems(items);
|
items = expandSecondPathItems(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupedItems = getGroupedItems({items: items, ...params})
|
const groupedItems = getGroupedItems({data: items, ...params, skipDuplicates: true})
|
||||||
.map(group => {
|
.map(group => {
|
||||||
const items = group.items.map(({ id, name, href }) => ({ id, name, logo: `${settings.global.website}/${href}` }))
|
const items = group.items.map(({ id, name, href }) => ({ id, name, logo: `${settings.global.website}/${href}` }))
|
||||||
return { ...group, items }
|
return { ...group, items }
|
||||||
})
|
})
|
||||||
return params.grouping === 'no' ? flattenItems(groupedItems) : groupedItems
|
return params.grouping === 'no' ? flattenItems(groupedItems) : groupedItems
|
||||||
}
|
}
|
||||||
|
|
||||||
// Netlify function
|
// Netlify function
|
||||||
export async function handler(event, context) {
|
module.exports.handler = async function(event) {
|
||||||
const body = processRequest(event.queryStringParameters)
|
const body = processRequest(event.queryStringParameters)
|
||||||
const headers = { 'Content-Type': 'application/json' }
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Credentials': true
|
||||||
|
}
|
||||||
return { statusCode: 200, body: JSON.stringify(body), headers }
|
return { statusCode: 200, body: JSON.stringify(body), headers }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,83 +1,64 @@
|
||||||
import React from 'react';
|
const _ = require('lodash');
|
||||||
import ReactDOMServer from 'react-dom/server';
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import _ from 'lodash';
|
const { millify, h } = require('../utils/format');
|
||||||
import assetPath from '../utils/assetPath';
|
const { fields } = require('../types/fields');
|
||||||
import { millify } from '../utils/format';
|
|
||||||
import fields from '../types/fields';
|
|
||||||
|
|
||||||
function getRelationStyle(relation) {
|
function getRelationStyle(relation) {
|
||||||
const relationInfo = fields.relation.valuesMap[relation]
|
const relationInfo = fields.relation.valuesMap[relation]
|
||||||
if (relationInfo && relationInfo.color) {
|
if (relationInfo && relationInfo.color) {
|
||||||
return {
|
return `border: 4px solid ${relationInfo.color};`;
|
||||||
border: '4px solid ' + relationInfo.color
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderDefaultCard({item}) {
|
module.exports.renderDefaultCard = function renderDefaultCard({item}) {
|
||||||
return ReactDOMServer.renderToStaticMarkup(getDefaultCard({item}));
|
return `
|
||||||
}
|
<div data-id="${h(item.id)}" class="mosaic-wrap">
|
||||||
|
<div class="mosaic ${item.oss ? '' : 'nonoss' }" style="${getRelationStyle(item.relation)}">
|
||||||
export function getDefaultCard({item}) {
|
<div class="logo_wrapper">
|
||||||
const card = (
|
<img loading="lazy" src="${assetPath(item.href)}" class="logo" max-height="100%" max-width="100%" alt="${h(item.name)}" />
|
||||||
<div data-id={item.id} className="mosaic-wrap" key={item.id}>
|
|
||||||
<div className={`mosaic ${item.oss ? '' : 'nonoss' }`} style={getRelationStyle(item.relation)}>
|
|
||||||
<div className="logo_wrapper">
|
|
||||||
<img loading="lazy" src={assetPath(item.href)} className='logo' max-height='100%' max-width='100%' alt={item.name} />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="mosaic-info">
|
<div class="mosaic-info">
|
||||||
<div className="mosaic-title">
|
<div class="mosaic-title">
|
||||||
<h5>{item.name}</h5>
|
<h5>${h(item.name)}</h5>
|
||||||
{item.organization}
|
${h(item.organization)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mosaic-stars">
|
<div class="mosaic-stars">
|
||||||
{ _.isNumber(item.stars) && item.stars &&
|
${_.isNumber(item.stars) && item.stars ?
|
||||||
<div>
|
`<div>
|
||||||
<span>★</span>
|
<span>★</span>
|
||||||
<span style={{position: 'relative', top: -3}}>{item.starsAsText}</span>
|
<span>${h(item.starsAsText)}</span>
|
||||||
</div>
|
</div>` : ''
|
||||||
}
|
}
|
||||||
{ Number.isInteger(item.amount) &&
|
${Number.isInteger(item.amount) ?
|
||||||
<div className="mosaic-funding">{item.amountKind === 'funding' ? 'Funding: ': 'MCap: '} {'$'+ millify( item.amount )}</div>
|
`<div class="mosaic-funding">${item.amountKind === 'funding' ? 'Funding: ': 'MCap: '} ${'$'+ h(millify(item.amount))}</div>` : ''
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
`;
|
||||||
return card;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderFlatCard({item}) {
|
module.exports.renderFlatCard = function renderFlatCard({item}) {
|
||||||
return ReactDOMServer.renderToStaticMarkup(getFlatCard({item}));
|
return `
|
||||||
}
|
<div data-id="${item.id}" class="mosaic-wrap">
|
||||||
|
<div class="mosaic">
|
||||||
export function getFlatCard({item}) {
|
<img loading="lazy" src="${assetPath(item.href)}" class="logo" alt="${h(item.name)}" />
|
||||||
const card = (
|
<div class="separator"></div>
|
||||||
<div data-id={item.id} className="mosaic-wrap" key={item.id} >
|
<h5>${h(item.flatName)}</h5>
|
||||||
<div className="mosaic">
|
|
||||||
<img loading="lazy" src={assetPath(item.href)} className='logo' alt={item.name} />
|
|
||||||
<div className="separator"/>
|
|
||||||
<h5>{item.flatName}</h5>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
`;
|
||||||
return card;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderBorderlessCard({item}) {
|
module.exports.renderBorderlessCard = function renderBorderlessCard({item}) {
|
||||||
return ReactDOMServer.renderToStaticMarkup(getBorderlessCard({item}));
|
return `
|
||||||
}
|
<div data-id="${item.id}" class="mosaic-wrap">
|
||||||
|
<div class="mosaic">
|
||||||
export function getBorderlessCard({item}) {
|
<img loading="lazy" src="${assetPath(item.href)}" class="logo" alt="${h(item.name)}" />
|
||||||
const card = (
|
|
||||||
<div data-id={item.id} className="mosaic-wrap" key={item.id}>
|
|
||||||
<div className="mosaic">
|
|
||||||
<img loading="lazy" src={assetPath(item.href)} className='logo' alt={item.name} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
`;
|
||||||
return card;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,40 @@
|
||||||
import React from 'react'
|
const getContrastRatio = require('get-contrast-ratio').default;
|
||||||
import GuideLink from './GuideLink'
|
|
||||||
import { categoryTitleHeight } from '../utils/landscapeCalculations'
|
|
||||||
import getContrastRatio from 'get-contrast-ratio'
|
|
||||||
|
|
||||||
const InternalLink = ({to, className, children, ...props}) =>
|
const { h } = require('../utils/format');
|
||||||
(<a data-type="internal" href={to} className={className} {...props}>{children}</a>)
|
const { renderGuideLink } = require('./GuideLink');
|
||||||
|
const { categoryTitleHeight } = require('../utils/landscapeCalculations');
|
||||||
|
|
||||||
const CategoryHeader = ({ href, label, guideAnchor, background, rotate = false }) => {
|
module.exports.renderCategoryHeader = function renderCategoryHeader({ href, label, guideAnchor, background, rotate = false }) {
|
||||||
const lowContrast = getContrastRatio('#ffffff', background) < 4.5
|
const lowContrast = getContrastRatio('#ffffff', background) < 4.5
|
||||||
const color = lowContrast ? '#282828' : '#ffffff'
|
const color = lowContrast ? '#282828' : '#ffffff'
|
||||||
const backgroundColor = lowContrast ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'
|
const backgroundColor = lowContrast ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'
|
||||||
// const infoEl = css.resolve`
|
|
||||||
// a {
|
|
||||||
// }
|
|
||||||
|
|
||||||
// a:hover {
|
return `
|
||||||
// color: ${color};
|
<a data-type="internal"
|
||||||
// background: none;
|
href="${href}"
|
||||||
// }
|
style="
|
||||||
|
display: flex;
|
||||||
// a :global(svg) {
|
justify-content: center;
|
||||||
// stroke: ${color};
|
align-items: center;
|
||||||
// }
|
text-align: center;
|
||||||
// `
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
return <>
|
font-size: 12px;
|
||||||
<InternalLink to={href} style={{
|
color: ${color};
|
||||||
display: 'flex',
|
background: none
|
||||||
justifyContent: 'center',
|
"
|
||||||
alignItems: 'center',
|
>${h(label)}</a>
|
||||||
textAlign: 'center',
|
${ guideAnchor ? renderGuideLink({label, anchor: guideAnchor, style: `
|
||||||
width: '100%',
|
width: ${categoryTitleHeight - 4}px;
|
||||||
flex: 1,
|
height: ${categoryTitleHeight - 4}px;
|
||||||
fontSize: '12px',
|
margin: 2px;
|
||||||
color: color,
|
display: flex;
|
||||||
background: 'none'
|
justify-content: center;
|
||||||
}}>{label}</InternalLink>
|
align-items: center;
|
||||||
{ guideAnchor && <
|
font-size: 18px;
|
||||||
GuideLink label={label} anchor={guideAnchor} style={{
|
color: ${color};
|
||||||
width: categoryTitleHeight - 4,
|
background: ${backgroundColor};
|
||||||
height: categoryTitleHeight - 4,
|
transform: ${rotate ? 'rotate(180deg)' : 'none' };
|
||||||
margin: '2px',
|
`}) : '' }
|
||||||
display: 'flex',
|
`;
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
fontSize: '18px',
|
|
||||||
color: color,
|
|
||||||
background: backgroundColor,
|
|
||||||
transform: rotate ? 'rotate(180deg)' : 'none'
|
|
||||||
}}
|
|
||||||
/> }
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CategoryHeader
|
|
||||||
|
|
|
@ -1,65 +1,64 @@
|
||||||
|
const _ = require('lodash');
|
||||||
// Render only for an export
|
// Render only for an export
|
||||||
import _ from 'lodash';
|
const { saneName } = require('../utils/saneName');
|
||||||
import React from 'react';
|
const { h } = require('../utils/format');
|
||||||
import ReactDOMServer from 'react-dom/server';
|
const { getGroupedItems, expandSecondPathItems } = require('../utils/itemsCalculator');
|
||||||
import getGroupedItems, { expandSecondPathItems } from '../utils/itemsCalculator'
|
const { parseParams } = require('../utils/routing');
|
||||||
import { parseParams } from '../utils/routing'
|
const { renderDefaultCard, renderBorderlessCard, renderFlatCard } = require('./CardRenderer');
|
||||||
import saneName from '../utils/saneName';
|
const icons = require('../utils/icons');
|
||||||
import { getDefaultCard, getBorderlessCard, getFlatCard } from './CardRenderer';
|
|
||||||
|
|
||||||
export function render({settings, items, exportUrl}) {
|
module.exports.render = function({items, exportUrl}) {
|
||||||
const params = parseParams(exportUrl.split('?').slice(-1)[0]);
|
const params = parseParams(exportUrl.split('?').slice(-1)[0]);
|
||||||
if (params.grouping === 'landscape') {
|
if (params.grouping === 'landscape') {
|
||||||
items = expandSecondPathItems(items);
|
items = expandSecondPathItems(items);
|
||||||
}
|
}
|
||||||
const groupedItems = getGroupedItems({data: items, ...params})
|
const groupedItems = getGroupedItems({data: items, ...params})
|
||||||
const cardStyle = params.cardStyle || 'default';
|
const cardStyle = params.cardStyle || 'default';
|
||||||
const cardFn = cardStyle === 'borderless' ? getBorderlessCard : cardStyle === 'flat' ? getFlatCard : getDefaultCard;
|
const cardFn = cardStyle === 'borderless' ? renderBorderlessCard : cardStyle === 'flat' ? renderFlatCard : renderDefaultCard;
|
||||||
const linkUrl = exportUrl.replace('&embed=yes', '').replace('embed=yes', '')
|
const linkUrl = exportUrl.replace('&embed=yes', '').replace('embed=yes', '')
|
||||||
|
|
||||||
const result = <>
|
const result = `
|
||||||
<div className="modal" style={{display: "none"}}>
|
<div class="modal" style="display: none;">
|
||||||
<div className="modal-shadow" />
|
<div class="modal-shadow"></div>
|
||||||
<div className="modal-container">
|
<div class="modal-container">
|
||||||
<div className="modal-body">
|
<div class="modal-body">
|
||||||
<div className="modal-buttons">
|
<div class="modal-buttons">
|
||||||
<a className="modal-close">x</a>
|
<a class="modal-close">x</a>
|
||||||
<span className="modal-prev"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></svg></span>
|
<span class="modal-prev">${icons.prev}</span>
|
||||||
<span className="modal-next"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></svg></span>
|
<span class="modal-next">${icons.next}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-content"></div>
|
<div class="modal-content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="home" className={"app " + cardStyle + "-mode" }>
|
<div id="home" class="app ${cardStyle}-mode">
|
||||||
<div className="app-overlay" />
|
<div class="app-overlay"></div>
|
||||||
<div className="main-parent">
|
<div class="main-parent">
|
||||||
<div className="app-overlay"></div>
|
<div class="app-overlay"></div>
|
||||||
<div className="main">
|
<div class="main">
|
||||||
<div className="cards-section">
|
<div class="cards-section">
|
||||||
<div className="column-content" >
|
<div class="column-content" >
|
||||||
{ groupedItems.map( (groupedItem) => {
|
${ groupedItems.map( (groupedItem) => {
|
||||||
const cardElements = groupedItem.items.map( (item) => cardFn({item}));
|
const uniqItems = _.uniqBy(groupedItem.items, (x) => x.name + x.logo);
|
||||||
const header = items.length > 0 ?
|
const cardElements = uniqItems.map( (item) => cardFn({item}));
|
||||||
<div className="sh_wrapper" data-wrapper-id={saneName(groupedItem.header)}>
|
const header = items.length > 0 ? `
|
||||||
<div style={{fontSize: '24px', paddingLeft: '16px', lineHeight: '48px', fontWeight: 500}}>
|
<div class="sh_wrapper" data-wrapper-id="${h(saneName(groupedItem.header))}">
|
||||||
<span>{groupedItem.header}</span>
|
<div style="font-size: 24px; padding-left: 16px; line-height: 48px; font-weight: 500;">
|
||||||
<span className="items-cont"> ({groupedItem.items.length})</span>
|
<span>${h(groupedItem.header)}</span>
|
||||||
|
<span class="items-cont"> (${uniqItems.length})</span>
|
||||||
</div>
|
</div>
|
||||||
</div> : null
|
</div>` : '';
|
||||||
return [ header, <div data-section-id={saneName(groupedItem.header)}>{cardElements}</div>];
|
return [ header, `<div data-section-id="${h(saneName(groupedItem.header))}">${cardElements.join('')}</div>`].join('');
|
||||||
})
|
}) }
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="embedded-footer">
|
<div id="embedded-footer">
|
||||||
<h1 style={{ marginTop: 20, width: '100%', textAlign: 'center' }}>
|
<h1 style="margin-top: 20px; width: 100%; text-align: center;">
|
||||||
<a data-type="external" target="_blank" href={linkUrl}>View</a> the full interactive landscape
|
<a data-type="external" target="_blank" href="${linkUrl}">View</a> the full interactive landscape
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
</>
|
return result;
|
||||||
return ReactDOMServer.renderToStaticMarkup(result);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,68 +1,61 @@
|
||||||
import React, { Fragment } from 'react';
|
const { calculateSize } = require("../utils/landscapeCalculations");
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
import { calculateSize } from "../utils/landscapeCalculations";
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
const headerHeight = 40;
|
const headerHeight = 40;
|
||||||
export function render({landscapeSettings, landscapeContent, version}) {
|
module.exports.render = function({landscapeSettings, landscapeContent, version}) {
|
||||||
const { fullscreenWidth, fullscreenHeight } = calculateSize(landscapeSettings);
|
const { fullscreenWidth, fullscreenHeight } = calculateSize(landscapeSettings);
|
||||||
const zoom = 1;
|
return `
|
||||||
return ReactDOMServer.renderToStaticMarkup(
|
<div class="gradient-bg" style="
|
||||||
<div className="gradient-bg" style={{
|
width: ${fullscreenWidth}px;
|
||||||
width: fullscreenWidth,
|
height: ${fullscreenHeight}px;
|
||||||
height: fullscreenHeight,
|
overflow: hidden;
|
||||||
overflow: 'hidden'
|
"><div class="inner-landscape" style="
|
||||||
}}>
|
width: ${fullscreenWidth}px;
|
||||||
<div className="inner-landscape" style={{
|
height: ${fullscreenHeight}px;
|
||||||
width: fullscreenWidth,
|
padding-top: ${headerHeight + 20}px;
|
||||||
height: fullscreenHeight,
|
padding-left: 20px;
|
||||||
paddingTop: headerHeight + 20,
|
position: relative;
|
||||||
paddingLeft: 20,
|
">
|
||||||
position: 'relative',
|
${landscapeContent }
|
||||||
}}>
|
<div style="
|
||||||
{ landscapeContent }
|
position: absolute;
|
||||||
<div style={{
|
top: 10px;
|
||||||
position: 'absolute',
|
left: 50%;
|
||||||
top: 10,
|
transform: translateX(-50%);
|
||||||
left: '50%',
|
font-size: 18px,
|
||||||
transform: 'translateX(-50%)',
|
background: rgb(64,89,163);
|
||||||
fontSize: 18,
|
color: white;
|
||||||
background: 'rgb(64,89,163)',
|
padding-left: 20px;
|
||||||
color: 'white',
|
padding-right: 20px;
|
||||||
paddingLeft: 20,
|
padding-top: 3px;
|
||||||
paddingRight: 20,
|
padding-bottom: 3px;
|
||||||
paddingTop: 3,
|
border-radius: 5px;
|
||||||
paddingBottom: 3,
|
">${landscapeSettings.fullscreen_header}</div>
|
||||||
borderRadius: 5
|
${ !landscapeSettings.fullscreen_hide_grey_logos ? `<div style="
|
||||||
}}>{landscapeSettings.fullscreen_header}</div>
|
position: absolute;
|
||||||
{ !landscapeSettings.fullscreen_hide_grey_logos && <div style={{
|
top: 15px;
|
||||||
position: 'absolute',
|
right: 12px;
|
||||||
top: 15,
|
font-size: 11px;
|
||||||
right: 12,
|
background: #eee;
|
||||||
fontSize: 11,
|
color: rgb(100,100,100);
|
||||||
background: '#eee',
|
padding-left: 20px;
|
||||||
color: 'rgb(100,100,100)',
|
padding-right: 20px;
|
||||||
paddingLeft: 20,
|
padding-top: 3px;
|
||||||
paddingRight: 20,
|
padding-bottom: 3px;
|
||||||
paddingTop: 3,
|
border-radius: 5px;
|
||||||
paddingBottom: 3,
|
">Greyed logos are not open source</div>` : '' }
|
||||||
borderRadius: 5
|
<div style="
|
||||||
}}>Greyed logos are not open source</div> }
|
position: absolute;
|
||||||
<div style={{
|
top: 10px;
|
||||||
position: 'absolute',
|
left: 15px;
|
||||||
top: 10,
|
font-size: 14px;
|
||||||
left: 15,
|
color: white;
|
||||||
fontSize: 14,
|
">${landscapeSettings.title} </div>
|
||||||
color: 'white',
|
<div style="
|
||||||
}}>{landscapeSettings.title} </div>
|
position: absolute;
|
||||||
<div style={{
|
top: 30px;
|
||||||
position: 'absolute',
|
left: 15px;
|
||||||
top: 30,
|
font-size: 12px;
|
||||||
left: 15,
|
color: #eee;
|
||||||
fontSize: 12,
|
">${version}</div>
|
||||||
color: '#eee',
|
|
||||||
}}>{version}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
import React from 'react'
|
const { h } = require('../utils/format');
|
||||||
|
const { guideLink } = require('../utils/icons');
|
||||||
const OutboundLink = ({to, className, children, ...props}) =>
|
module.exports.renderGuideLink = function({anchor, label, style }) {
|
||||||
(<a data-type="external" target="_blank" href={to} className={className} {...props}>{children}</a>)
|
|
||||||
|
|
||||||
const GuideLink = ({ anchor, label, className="", ...props }) => {
|
|
||||||
const svg = <svg viewBox="0 0 24 24" aria-hidden="true">
|
|
||||||
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
|
|
||||||
</svg>;
|
|
||||||
const ariaLabel = `Read more about ${label} on the guide`
|
const ariaLabel = `Read more about ${label} on the guide`
|
||||||
const to = `guide#${anchor}`;
|
const to = h(`guide#${anchor}`);
|
||||||
|
|
||||||
return <OutboundLink className={className} to={to} aria-label={ariaLabel} {...props}>
|
return `<a data-type="external" target="_blank" style="${style}" aria-label="${h(ariaLabel)}" href="${to}">
|
||||||
{svg}
|
${guideLink}
|
||||||
</OutboundLink>
|
</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GuideLink
|
|
||||||
|
|
|
@ -1,99 +1,94 @@
|
||||||
import React from 'react';
|
const _ = require('lodash');
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
|
|
||||||
import { isLargeFn } from '../utils/landscapeCalculations'
|
const { sizeFn } = require('../utils/landscapeCalculations');
|
||||||
import Item from './Item.js'
|
const { renderItem } = require('./Item.js');
|
||||||
import _ from 'lodash';
|
const { h } = require('../utils/format');
|
||||||
import { guideIcon } from '../icons';
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import assetPath from '../utils/assetPath';
|
const icons = require('../utils/icons');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// guide is a guide index
|
// guide is a guide index
|
||||||
export function render({settings, items, guide }) {
|
module.exports.render = function({settings, items, guide}) {
|
||||||
const Title = () => <h1 className="title">{settings.global.short_name} Landscape Guide</h1>;
|
const currentBranch = require('child_process').execSync(`git rev-parse --abbrev-ref HEAD`, {
|
||||||
const SubcategoryMetadata = ({ node, entries }) => {
|
cwd: require('../../tools/settings').projectPath
|
||||||
const orderedEntries = _.orderBy(entries, (x) => !x.isLarge);
|
}).toString().trim();
|
||||||
|
|
||||||
|
|
||||||
|
const title = `<h1 className="title" style="margin-top: -5px;">${h(settings.global.short_name)} Landscape Guide</h1>`;
|
||||||
|
const renderSubcategoryMetadata = ({ node, entries }) => {
|
||||||
|
const orderedEntries = _.orderBy(entries, (x) => -x.size);
|
||||||
const projectEntries = entries.filter(entry => entry.project)
|
const projectEntries = entries.filter(entry => entry.project)
|
||||||
return <>
|
return `
|
||||||
{ (node.buzzwords.length > 0 || projectEntries.length > 0) && <div className="metadata">
|
${ (node.buzzwords.length > 0 || projectEntries.length > 0) ? `<div class="metadata">
|
||||||
<div className="header">
|
<div class="header">
|
||||||
<div>Buzzwords</div>
|
<div>Buzzwords</div>
|
||||||
<div>{settings.global.short_name} Projects</div>
|
<div>${h(settings.global.short_name)} Projects</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="body">
|
<div class="body">
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
{ node.buzzwords.map(str => <li key={str}>{str}</li>) }
|
${ node.buzzwords.map(str => `<li>${h(str)}</li>`).join('') }
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
{ projectEntries.map(entry => <li key={entry.name}>{entry.name} ({entry.project})</li>) }
|
${ projectEntries.map(entry => `<li>${h(entry.name)} (${h(entry.project)})</li>`).join('') }
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div> }
|
</div> ` : '' }
|
||||||
|
|
||||||
<div className="items">
|
<div class="items">
|
||||||
{ orderedEntries.map(entry => <Item item={entry} key={entry.id} />) }
|
${ orderedEntries.map(entry => renderItem(entry)).join('') }
|
||||||
</div>
|
</div>
|
||||||
</>
|
`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const SidebarLink = ({ anchor, level, className = "", children }) => {
|
const renderNavigation = ({ nodes }) => {
|
||||||
const paddingLeft = 20 + (level - 1) * 10;
|
|
||||||
return <a href={`#${anchor}`} data-level={level} className={`sidebar-link ${className}`} style={{ paddingLeft }}>
|
|
||||||
{ children }
|
|
||||||
</a>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Navigation = ({ nodes }) => {
|
|
||||||
const links = nodes.filter(node => node.anchor)
|
const links = nodes.filter(node => node.anchor)
|
||||||
const parents = links
|
const parents = links
|
||||||
.map(n => n.anchor.split('--')[0])
|
.map(n => n.anchor.split('--')[0])
|
||||||
.reduce((acc, n) => ({ ...acc, [n]: (acc[n] || 0) + 1}), {})
|
.reduce((acc, n) => ({ ...acc, [n]: (acc[n] || 0) + 1}), {})
|
||||||
|
|
||||||
return links
|
return links
|
||||||
.filter(({ title, level, anchor }) => {
|
.filter(({ title }) => {
|
||||||
return title
|
return title
|
||||||
})
|
})
|
||||||
.map(node => {
|
.map(node => {
|
||||||
const hasChildren = (parents[node.anchor] || 0) > 1
|
const hasChildren = (parents[node.anchor] || 0) > 1
|
||||||
return <React.Fragment key={node.anchor}>
|
return `
|
||||||
<SidebarLink className="expandable" anchor={node.anchor} level={node.level}>
|
<a href="#${node.anchor}" data-level="${node.level}" class="sidebar-link expandable" style="padding-left: ${10 + node.level * 10}px;">
|
||||||
{node.title} { hasChildren && <svg viewBox="0 0 24 24"><path d="M10 17l5-5-5-5v10z"></path></svg> }
|
${h(node.title)} ${hasChildren ? icons.expand : ''}
|
||||||
</SidebarLink>
|
</a>
|
||||||
{hasChildren && <SidebarLink anchor={node.anchor} level={node.level + 1} >
|
${hasChildren ? `
|
||||||
Overview
|
<a href="#${node.anchor}" data-level=${node.level + 1} class="sidebar-link" style="padding-left: 30px;"> Overview </a>
|
||||||
</SidebarLink>}
|
` : ''}
|
||||||
</React.Fragment>
|
`}).join('');
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LandscapeLink = ({ landscapeKey, title }) => {
|
const renderLandscapeLink = ({ landscapeKey, title }) => {
|
||||||
const href = `card-mode?category=${landscapeKey}`
|
const href = `card-mode?category=${landscapeKey}`
|
||||||
return <a href={href} target="_blank" className="permalink">
|
return `<a href="${href}" target="_blank" class="permalink">${icons.guide} ${h(title)} </a>`;
|
||||||
{guideIcon} {title}
|
|
||||||
</a>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Content = ({ nodes, enhancedEntries }) => {
|
const renderContent = ({ nodes, enhancedEntries }) => {
|
||||||
return nodes.map((node, idx) => {
|
return nodes.map((node) => {
|
||||||
const subcategoryEntries = node.subcategory && enhancedEntries.filter(entry => entry.path.split(' / ')[1].trim() === node.title) || []
|
const subcategoryEntries = node.subcategory && enhancedEntries.filter(entry => entry.path.split(' / ')[1].trim() === node.title) || [];
|
||||||
|
return `<div>
|
||||||
return <div key={idx}>
|
${ node.title ? `<div class="section-title" id="${h(node.anchor)}">
|
||||||
{ node.title && <div className="section-title" id={node.anchor}>
|
<h2 data-variant="${node.level + 1}">
|
||||||
<h2 data-variant={node.level + 1}>
|
${ node.landscapeKey
|
||||||
{ node.landscapeKey ?
|
? renderLandscapeLink({landscapeKey: node.landscapeKey, title: node.title})
|
||||||
<LandscapeLink landscapeKey={node.landscapeKey} title={node.title} /> :
|
: h(node.title)
|
||||||
node.title }
|
}
|
||||||
</h2>
|
</h2>
|
||||||
</div>}
|
</div>
|
||||||
{ node.content && <div className="guide-content" dangerouslySetInnerHTML={{ __html: node.content }} /> }
|
` : ''}
|
||||||
{ node.subcategory && <SubcategoryMetadata entries={subcategoryEntries} node={node} /> }
|
${ node.content ? `<div class="guide-content">${node.content}</div>` : ''}
|
||||||
</div>
|
${ node.subcategory ? renderSubcategoryMetadata({entries: subcategoryEntries,node:node}) : '' }
|
||||||
})
|
</div>`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const enhancedEntries = items.map( (entry) => {
|
const enhancedEntries = items.map( (entry) => {
|
||||||
|
@ -115,58 +110,66 @@ export function render({settings, items, guide }) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const enhanced = { ...entry, categoryAttrs }
|
const enhanced = { ...entry, categoryAttrs }
|
||||||
return { ...enhanced, isLarge: isLargeFn(enhanced) }
|
return { ...enhanced, size: sizeFn(enhanced) }
|
||||||
}).filter( (x) => !!x);
|
}).filter( (x) => !!x);
|
||||||
|
|
||||||
return ReactDOMServer.renderToStaticMarkup (
|
return `
|
||||||
<>
|
<div class="links">
|
||||||
<div className="side-content">
|
<div>
|
||||||
<span className="landscape-logo">
|
<a href="${(settings.global.self_hosted_repo || false) ? "" : "https://github.com/"}${settings.global.repo}/edit/${currentBranch}/guide.md" target="_blank">
|
||||||
<a className="nav-link" href="/">
|
${icons.edit}
|
||||||
<img src={assetPath("images/left-logo.svg")} />
|
Edit this page</a>
|
||||||
|
</div>
|
||||||
|
<div style="height: 5px;"></div>
|
||||||
|
<div>
|
||||||
|
<a href="${(settings.global.self_hosted_repo || false) ? "" : "https://github.com/"}${settings.global.repo}/issues/new?title=Guide Issue" target="_blank">
|
||||||
|
${icons.github}
|
||||||
|
Report issue</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="side-content">
|
||||||
|
<span class="landscape-logo">
|
||||||
|
<a aria-label="reset filters" class="nav-link" href="/">
|
||||||
|
<img alt="landscape logo" src="${assetPath("images/left-logo.svg")} ">
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<div className="guide-sidebar">
|
<div class="guide-sidebar">
|
||||||
<div className="sidebar-collapse">+</div>
|
<div class="sidebar-collapse">+</div>
|
||||||
<div className="guide-toggle">
|
<div class="guide-toggle">
|
||||||
<span className="toggle-item "><a href="./">Landscape</a></span>
|
<span class="toggle-item "><a href="./">Landscape</a></span>
|
||||||
<span className="toggle-item active">Guide</span>
|
<span class="toggle-item active">Guide</span>
|
||||||
</div>
|
</div>
|
||||||
<Navigation nodes={guide} />
|
${renderNavigation({nodes: guide})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="guide-header">
|
<div class="guide-header">
|
||||||
<div className="container">
|
<div class="container">
|
||||||
<div className="content">
|
<div class="content">
|
||||||
<button className="sidebar-show">
|
<button class="sidebar-show" role="none" aria-label="show sidebar">${icons.sidebar}</button>
|
||||||
<svg viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
|
<span class="landscape-logo">
|
||||||
</button>
|
<a aria-label="reset filters" class="nav-link" href="/">
|
||||||
<span className="landscape-logo">
|
<img alt="landscape logo" src="${assetPath("/images/left-logo.svg")}">
|
||||||
<a className="nav-link" href="/">
|
|
||||||
<img src={assetPath("/images/left-logo.svg")} />
|
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<Title />
|
${title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a rel="noopener noreferrer noopener noreferrer"
|
<a rel="noopener noreferrer noopener noreferrer"
|
||||||
className="landscapeapp-logo"
|
class="landscapeapp-logo"
|
||||||
title={settings.global.short_name}
|
title="${h(settings.global.short_name)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={settings.global.company_url}>
|
href="${settings.global.company_url}">
|
||||||
<img src={assetPath("images/right-logo.svg")} title={settings.global.short_name}/>
|
<img src="${assetPath("images/right-logo.svg")}" title="${settings.global.short_name}">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="main-content">
|
<div class="main-content">
|
||||||
<div className="container">
|
<div class="container">
|
||||||
<div className="content">
|
<div class="content">
|
||||||
<Title />
|
${renderContent({nodes: guide,enhancedEntries: enhancedEntries})}
|
||||||
<Content nodes={guide} enhancedEntries={enhancedEntries} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
`;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,79 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import React from 'react';
|
const { h } = require('../utils/format');
|
||||||
import ReactDOMServer from 'react-dom/server';
|
const { fields, sortOptions, options } = require('../types/fields');
|
||||||
import fields, { sortOptions, options } from '../types/fields'
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import assetPath from '../utils/assetPath';
|
const icons = require('../utils/icons');
|
||||||
|
|
||||||
const OutboundLink = ({to, className, children}) =>
|
const renderSingleSelect = ({name, options, title}) => (
|
||||||
(<a data-type="external" target="_blank" href={to} className={className}>{children}</a>)
|
`
|
||||||
|
<div class="select" data-type="single" data-name="${name}" data-options="${h(JSON.stringify(options))}">
|
||||||
const SingleSelect = ({name, options, title}) => (
|
<select class="select-text" required>
|
||||||
<div className="select" data-type="single" data-name={name} data-options={JSON.stringify(options)}>
|
<option value="1" selected>Value</option>
|
||||||
<select className="select-text" required>
|
</select>
|
||||||
<option value="1" selected>Value</option>
|
<span class="select-highlight"></span>
|
||||||
</select>
|
<span class="select-bar"></span>
|
||||||
<span className="select-highlight"></span>
|
<label class="select-label">${h(title)}</label>
|
||||||
<span className="select-bar"></span>
|
</div>
|
||||||
<label className="select-label">{title}</label>
|
`
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
const MultiSelect = ({name, options, title}) => (
|
const renderMultiSelect = ({name, options, title}) => (
|
||||||
<div className="select" data-type="multi" data-name={name} data-options={JSON.stringify(options)}>
|
`
|
||||||
<select className="select-text" required>
|
<div class="select" data-type="multi" data-name="${name}" data-options="${h(JSON.stringify(options))}">
|
||||||
<option value="1" selected>Value</option>
|
<select class="select-text" required>
|
||||||
</select>
|
<option value="1" selected>Value</option>
|
||||||
<span className="select-highlight"></span>
|
</select>
|
||||||
<span className="select-bar"></span>
|
<span class="select-highlight"></span>
|
||||||
<label className="select-label">{title}</label>
|
<span class="select-bar"></span>
|
||||||
</div>
|
<label class="select-label">${h(title)}</label>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
)
|
)
|
||||||
|
|
||||||
const GroupingSelect = function() {
|
const renderGroupingSelect = function() {
|
||||||
const groupingFields = ['landscape', 'relation', 'license', 'organization', 'headquarters'];
|
const groupingFields = ['landscape', 'relation', 'license', 'organization', 'headquarters'];
|
||||||
const options = [{
|
const options = [{
|
||||||
id: 'no',
|
id: 'no',
|
||||||
label: 'No Grouping',
|
label: 'No Grouping',
|
||||||
}].concat(groupingFields.map(id => ({ id: fields[id].url, label: fields[id].groupingLabel })))
|
}].concat(groupingFields.map(id => ({ id: fields[id].url, label: (fields[id].groupingLabel) })))
|
||||||
return <SingleSelect name="grouping" options={options} title="Grouping" />
|
return renderSingleSelect({name: "grouping", options, title: "Grouping" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const SortBySelect = function() {
|
const renderSortBySelect = function() {
|
||||||
const options = sortOptions.filter( (x) => !x.disabled).map( (x) => ({
|
const options = sortOptions.filter( (x) => !x.disabled).map( (x) => ({
|
||||||
id: (fields[x.id] || { url: x.id}).url || x.id, label: x.label
|
id: (fields[x.id] || { url: x.id}).url || x.id, label: x.label
|
||||||
}))
|
}))
|
||||||
return <SingleSelect name="sort" options={options} title="Sort By" />
|
return renderSingleSelect({name: "sort", options, title: "Sort By" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterCategory = function() {
|
const renderFilterCategory = function() {
|
||||||
return <MultiSelect name="category" options={options('landscape')} title="Category" />;
|
return renderMultiSelect({name:"category", options: options('landscape'), title: 'Category'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterProject = function() {
|
const renderFilterProject = function() {
|
||||||
return <MultiSelect name="project" options={options('relation')} title="Project" />;
|
return renderMultiSelect({name:"project", options: options('relation'), title: 'Project'});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterLicense = function() {
|
const renderFilterLicense = function() {
|
||||||
return <MultiSelect name="license" options={options('license')} title="License" />;
|
return renderMultiSelect({name:"license", options: options('license'), title: "License"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterOrganization = function() {
|
const renderFilterOrganization = function() {
|
||||||
return <MultiSelect name="organization" options={options('organization')} title="Organization" />;
|
return renderMultiSelect({name: "organization", options: options('organization'), title: "Organization"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterHeadquarters = function() {
|
const renderFilterHeadquarters = function() {
|
||||||
return <MultiSelect name="headquarters" options={options('headquarters')} title="Headquarters" />;
|
return renderMultiSelect({name: "headquarters", options: options('headquarters'), title: "Headquarters"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterCompanyType = function() {
|
const renderFilterCompanyType = function() {
|
||||||
return <MultiSelect name="company-type" options={options('companyType')} title="Company Type" />;
|
return renderMultiSelect({name: "company-type", options: options('companyType'), title: "Company Type"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const FilterIndustries = function() {
|
const renderFilterIndustries = function() {
|
||||||
return <MultiSelect name="industries" options={options('industries')} title="Industry" />;
|
return renderMultiSelect({name: "industries", options: options('industries'), title: "Industry"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.render = function({settings, guidePayload, hasGuide, bigPictureKey}) {
|
||||||
|
|
||||||
export function render({settings, guidePayload, hasGuide, bigPictureKey}) {
|
|
||||||
const mainCard = [{shortTitle: 'Card', title: 'Card Mode', mode: 'card', url: 'card-mode', tabIndex: 0}]
|
const mainCard = [{shortTitle: 'Card', title: 'Card Mode', mode: 'card', url: 'card-mode', tabIndex: 0}]
|
||||||
const landscapes = Object.values(settings.big_picture).map(function(section) {
|
const landscapes = Object.values(settings.big_picture).map(function(section) {
|
||||||
return {
|
return {
|
||||||
|
@ -88,164 +87,151 @@ export function render({settings, guidePayload, hasGuide, bigPictureKey}) {
|
||||||
const tabs = _.orderBy(mainCard.concat(landscapes), 'tabIndex').map( item => _.pick(item, ['title', 'mode', 'shortTitle', 'url']))
|
const tabs = _.orderBy(mainCard.concat(landscapes), 'tabIndex').map( item => _.pick(item, ['title', 'mode', 'shortTitle', 'url']))
|
||||||
|
|
||||||
|
|
||||||
const result = <>
|
return `
|
||||||
<div className="select-popup" style={{display: "none"}}>
|
<div class="select-popup" style="display: none;">
|
||||||
<div className="select-popup-body"/>
|
<div class="select-popup-body" ></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal" style={{display: "none"}}>
|
<div class="modal" style="display: none;">
|
||||||
<div className="modal-shadow" />
|
<div class="modal-shadow" ></div>
|
||||||
<div className="modal-container">
|
<div class="modal-container">
|
||||||
<div className="modal-body">
|
<div class="modal-body">
|
||||||
<div className="modal-buttons">
|
<div class="modal-buttons">
|
||||||
<a className="modal-close">x</a>
|
<a class="modal-close">x</a>
|
||||||
<span className="modal-prev"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></svg></span>
|
<span class="modal-prev">${icons.prev}</span>
|
||||||
<span className="modal-next"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></svg></span>
|
<span class="modal-next">${icons.next}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="modal-content"></div>
|
<div class="modal-content"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="guide-page" style={{display: guidePayload ? "" : "none"}} data-loaded={guidePayload ? "true" : ""}>
|
<div id="guide-page" style="display: ${guidePayload ? "" : "none"};" data-loaded="${guidePayload ? "true" : ""}">
|
||||||
{ !guidePayload && <div className="side-content">
|
${ !guidePayload ? `<div class="side-content">
|
||||||
<span className="landscape-logo">
|
<span class="landscape-logo">
|
||||||
<a className="nav-link" href="/">
|
<a aria-label="reset filters" class="nav-link" href="/">
|
||||||
<img src={assetPath("images/left-logo.svg")} />
|
<img alt="landscape logo" src="${assetPath("images/left-logo.svg")}" />
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<div className="guide-sidebar">
|
<div class="guide-sidebar">
|
||||||
<div className="sidebar-collapse">X</div>
|
<div class="sidebar-collapse">X</div>
|
||||||
<div className="guide-toggle">
|
<div class="guide-toggle">
|
||||||
<span className="toggle-item "><a href="./">Landscape</a></span>
|
<span class="toggle-item "><a href="./">Landscape</a></span>
|
||||||
<span className="toggle-item active">Guide</span>
|
<span class="toggle-item active">Guide</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>` : ''
|
||||||
}
|
}
|
||||||
{ guidePayload && "$$guide$$" }
|
${ guidePayload ? "$$guide$$" : ''}
|
||||||
</div>
|
</div>
|
||||||
<div id="home" style={{display: guidePayload ? "none" : ""}} className="app">
|
<div id="home" style="display: ${guidePayload ? "none" : ""}" class="app">
|
||||||
<div className="app-overlay" />
|
<div class="app-overlay"></div>
|
||||||
<div className="main-parent">
|
<div class="main-parent">
|
||||||
<button className="sidebar-show">
|
<button class="sidebar-show" role="none" aria-label="show sidebar">${icons.sidebar}</button>
|
||||||
<svg viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
|
<div class="header_container">
|
||||||
</button>
|
<div class="header">
|
||||||
<div className="header_container">
|
<span class="landscape-logo">
|
||||||
<div className="header">
|
<a aria-label="reset filters" class="nav-link" href="/">
|
||||||
<span className="landscape-logo">
|
<img alt="landscape logo" src="${assetPath("images/left-logo.svg")}" />
|
||||||
<a className="nav-link" href="/">
|
|
||||||
<img src={assetPath("images/left-logo.svg")} />
|
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<a rel="noopener noreferrer noopener noreferrer"
|
<a rel="noopener noreferrer noopener noreferrer"
|
||||||
className="landscapeapp-logo"
|
class="landscapeapp-logo"
|
||||||
title={settings.global.short_name}
|
title="${h(settings.global.short_name)}"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href={settings.global.company_url}>
|
href="${settings.global.company_url}">
|
||||||
<img src={assetPath("/images/right-logo.svg")} title={settings.global.short_name}/>
|
<img src="${assetPath("/images/right-logo.svg")}" title="${h(settings.global.short_name)}" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sidebar">
|
<div class="sidebar">
|
||||||
<div className="sidebar-scroll">
|
<div class="sidebar-scroll">
|
||||||
<div className="sidebar-collapse">+</div>
|
<div class="sidebar-collapse">+</div>
|
||||||
{ hasGuide &&
|
${ hasGuide ? `
|
||||||
<div className="guide-toggle">
|
<div class="guide-toggle">
|
||||||
<span className="toggle-item active">Landscape</span>
|
<span class="toggle-item active">Landscape</span>
|
||||||
<span className="toggle-item "><a href="/guide">Guide</a></span>
|
<span class="toggle-item "><a href="/guide">Guide</a></span>
|
||||||
</div>
|
</div> ` : ''
|
||||||
}
|
}
|
||||||
<a className="filters-action reset-filters">
|
<a class="filters-action reset-filters">${icons.reset}<span>Reset Filters</span>
|
||||||
<svg viewBox="0 0 24 24"><path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"/></svg>
|
|
||||||
<span>Reset Filters</span>
|
|
||||||
</a>
|
</a>
|
||||||
<GroupingSelect />
|
${renderGroupingSelect()}
|
||||||
<SortBySelect />
|
${renderSortBySelect()}
|
||||||
<FilterCategory />
|
${renderFilterCategory()}
|
||||||
<FilterProject />
|
${renderFilterProject()}
|
||||||
<FilterLicense />
|
${renderFilterLicense()}
|
||||||
<FilterOrganization />
|
${renderFilterOrganization()}
|
||||||
<FilterHeadquarters />
|
${renderFilterHeadquarters()}
|
||||||
<FilterCompanyType />
|
${renderFilterCompanyType()}
|
||||||
<FilterIndustries />
|
${renderFilterIndustries()}
|
||||||
<a className="filters-action export">
|
|
||||||
<svg viewBox="0 0 24 24">
|
<div class="sidebar-presets">
|
||||||
<path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14zm-1-6h-3V8h-2v5H8l4 4 4-4z" />
|
|
||||||
</svg>
|
|
||||||
<span>Download as CSV</span>
|
|
||||||
</a>
|
|
||||||
<div className="sidebar-presets">
|
|
||||||
<h4>Example filters</h4>
|
<h4>Example filters</h4>
|
||||||
{ (settings.presets || []).map(preset =>
|
${ (settings.presets || []).map(preset => `
|
||||||
<a data-type="internal" className="preset" href={preset.url}>
|
<a data-type="internal" class="preset" href="${preset.url}">
|
||||||
{preset.label}
|
${h(preset.label)}
|
||||||
</a>
|
</a> `
|
||||||
)}
|
).join('')}
|
||||||
{ (settings.ads || []).map( (entry) => (
|
|
||||||
<OutboundLink className="sidebar-event" key={entry.image} to={entry.url} title={entry.title}>
|
|
||||||
<img src={assetPath(entry.image)} alt={entry.title} />
|
|
||||||
</OutboundLink>
|
|
||||||
)) }
|
|
||||||
</div>
|
</div>
|
||||||
|
${ (settings.ads || []).map( (entry) => `
|
||||||
|
<a data-type="external" target="_blank" class="sidebar-event" href="${entry.url}" title="${h(entry.title)}">
|
||||||
|
<img src="${assetPath(entry.image)}" alt="${entry.title}" />
|
||||||
|
</a>
|
||||||
|
`).join('') }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="app-overlay"></div>
|
<div class="app-overlay"></div>
|
||||||
|
|
||||||
<div className="main">
|
<div class="main">
|
||||||
<div className="disclaimer">
|
<div class="disclaimer">
|
||||||
<span dangerouslySetInnerHTML={{__html: settings.home.header}} />
|
<span> ${settings.home.header} </span>
|
||||||
Please <OutboundLink to={`https://github.com/${settings.global.repo}`}>open</OutboundLink> a pull request to
|
Please <a data-type="external" target="_blank" href="${(settings.global.self_hosted_repo || false) ? "" : "https://github.com/"}${settings.global.repo}">open</a> a pull request to
|
||||||
correct any issues. Greyed logos are not open source. Last Updated: {process.env.lastUpdated}
|
correct any issues. Greyed logos are not open source. Last Updated: ${process.env.lastUpdated}
|
||||||
</div>
|
</div>
|
||||||
<h4 className="summary" />
|
<h4 class="summary"></h4>
|
||||||
<div className="cards-section">
|
<div class="cards-section">
|
||||||
|
<div class="big-picture-switch big-picture-switch-normal">
|
||||||
<div className="big-picture-switch big-picture-switch-normal">
|
${ tabs.map( (tab) => `
|
||||||
{ tabs.map( (tab) => <a href={tab.url} data-mode={tab.mode} key={tab.mode}><div>{tab.title}</div></a> )}
|
<a href="${tab.url}" data-mode="${tab.mode}"><div>${h(tab.title)}</div></a>
|
||||||
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="right-buttons">
|
<div class="right-buttons">
|
||||||
<div className="fullscreen-exit">
|
<div class="fullscreen-exit">${icons.fullscreenExit}</div>
|
||||||
<svg viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></svg>
|
<div class="fullscreen-enter">${icons.fullscreenEnter}</div>
|
||||||
</div>
|
<div class="zoom-out">${icons.zoomOut}</div>
|
||||||
<div className="fullscreen-enter">
|
<div class="zoom-reset"></div>
|
||||||
<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></svg>
|
<div class="zoom-in">${icons.zoomIn}</div>
|
||||||
</div>
|
|
||||||
<div className="zoom-out">
|
|
||||||
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"></path></svg>
|
|
||||||
</div>
|
|
||||||
<div className="zoom-reset"></div>
|
|
||||||
<div className="zoom-in">
|
|
||||||
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ tabs.filter( (x) => x.mode !== 'card').map( (tab) =>
|
${ tabs.filter( (x) => x.mode !== 'card').map( (tab) => `
|
||||||
<div data-mode={tab.mode} className="landscape-flex">
|
<div data-mode="${tab.mode}" class="landscape-flex">
|
||||||
<div className="landscape-wrapper">
|
<div class="landscape-wrapper">
|
||||||
<div className="inner-landscape" style={{padding: 10, display: "none"}} >
|
<div class="inner-landscape" style="padding: 10px; display: none;">
|
||||||
{ bigPictureKey === tab.mode && '$$' + bigPictureKey + '$$'}
|
${ bigPictureKey === tab.mode ? '$$' + bigPictureKey + '$$' : ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`).join('')}
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="column-content" />
|
<div class="column-content"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer" style={{ marginTop: 10, fontSize:'9pt', width: '100%', textAlign: 'center' }}>
|
<div id="footer" style="
|
||||||
{settings.home.footer} For more information, please see the
|
margin-top: 10px;
|
||||||
<OutboundLink eventLabel="crunchbase-terms" to={`https://github.com/${settings.global.repo}/blob/HEAD/README.md#license`}>
|
font-size: 9pt;
|
||||||
license
|
width: 100%;
|
||||||
</OutboundLink> info.
|
text-align: center;">
|
||||||
|
${h(settings.home.footer)} For more information, please see the
|
||||||
|
<a data-type="external" target="_blank" eventLabel="crunchbase-terms" href="${(settings.global.self_hosted_repo || false) ? "" : "https://github.com/"}${settings.global.repo}/blob/HEAD/README.md#license">
|
||||||
|
license
|
||||||
|
</a> info.
|
||||||
</div>
|
</div>
|
||||||
<div id="embedded-footer">
|
<div id="embedded-footer">
|
||||||
<h1 style={{ marginTop: 20, width: '100%', textAlign: 'center' }}>
|
<h1 style="margin-top: 20px; width: 100%; text-align: center;">
|
||||||
<OutboundLink to="url">View</OutboundLink> the full interactive landscape
|
<a data-type="external" target="_blank" href="url">View</a> the full interactive landscape
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
`;
|
||||||
return ReactDOMServer.renderToStaticMarkup(result);
|
}
|
||||||
};
|
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import React, { Fragment, useContext } from "react";
|
const { renderItem } = require("./Item");
|
||||||
import Item from "./Item";
|
const { h } = require('../utils/format');
|
||||||
|
const {
|
||||||
const InternalLink = ({to, className, children}) =>
|
|
||||||
(<a data-type="internal" href={to} className={className}>{children}</a>)
|
|
||||||
|
|
||||||
import {
|
|
||||||
calculateHorizontalCategory,
|
calculateHorizontalCategory,
|
||||||
categoryBorder,
|
categoryBorder,
|
||||||
categoryTitleHeight,
|
categoryTitleHeight,
|
||||||
|
@ -14,107 +10,111 @@ import {
|
||||||
smallItemHeight,
|
smallItemHeight,
|
||||||
subcategoryMargin,
|
subcategoryMargin,
|
||||||
subcategoryTitleHeight
|
subcategoryTitleHeight
|
||||||
} from "../utils/landscapeCalculations";
|
} = require("../utils/landscapeCalculations");
|
||||||
import SubcategoryInfo from './SubcategoryInfo'
|
const { renderSubcategoryInfo } = require('./SubcategoryInfo');
|
||||||
import CategoryHeader from './CategoryHeader'
|
const { renderCategoryHeader } = require('./CategoryHeader');
|
||||||
|
|
||||||
const Divider = ({ color }) => {
|
const renderDivider = (color) => {
|
||||||
const width = dividerWidth
|
const width = dividerWidth;
|
||||||
const marginTop = 2 * subcategoryMargin
|
const marginTop = 2 * subcategoryMargin;
|
||||||
const height = `calc(100% - ${2 * marginTop}px)`
|
const height = `calc(100% - ${2 * marginTop}px)`;
|
||||||
|
|
||||||
return <div style={{ width, marginTop, height, borderLeft: `${width}px solid ${color}` }}/>
|
return `<div style="
|
||||||
|
width: ${width}px;
|
||||||
|
margin-top: ${marginTop}px;
|
||||||
|
height: ${height};
|
||||||
|
border-left: ${width}px solid ${color}
|
||||||
|
"></div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HorizontalCategory = ({ header, guideInfo, subcategories, width, height, top, left, color, href, fitWidth }) => {
|
module.exports.renderHorizontalCategory = function({ header, guideInfo, subcategories, width, height, top, left, color, href, fitWidth }) {
|
||||||
const addInfoIcon = !!guideInfo;
|
const addInfoIcon = !!guideInfo;
|
||||||
const subcategoriesWithCalculations = calculateHorizontalCategory({ height, width, subcategories, fitWidth, addInfoIcon })
|
const subcategoriesWithCalculations = calculateHorizontalCategory({ height, width, subcategories, fitWidth, addInfoIcon })
|
||||||
const totalRows = Math.max(...subcategoriesWithCalculations.map(({ rows }) => rows))
|
const totalRows = Math.max(...subcategoriesWithCalculations.map(({ rows }) => rows))
|
||||||
|
|
||||||
return (
|
return `
|
||||||
<div style={{ width, left, height, top, position: 'absolute' }} className="big-picture-section">
|
<div style="
|
||||||
|
width: ${width}px;
|
||||||
|
left: ${left}px;
|
||||||
|
height: ${height}px;
|
||||||
|
top: ${top}px;
|
||||||
|
position: absolute;
|
||||||
|
" class="big-picture-section">
|
||||||
<div
|
<div
|
||||||
style={{
|
style="
|
||||||
position: 'absolute',
|
position: absolute;
|
||||||
background: color,
|
background: ${color};
|
||||||
top: subcategoryTitleHeight,
|
top: ${subcategoryTitleHeight}px;
|
||||||
bottom: 0,
|
bottom: 0;
|
||||||
left: 0,
|
left: 0;
|
||||||
right: 0,
|
right: 0;
|
||||||
boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2)',
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);
|
||||||
padding: categoryBorder
|
padding: ${categoryBorder}px;
|
||||||
}}
|
"
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style="
|
||||||
top: 0,
|
top: 0;
|
||||||
bottom: 0,
|
bottom: 0;
|
||||||
left: 0,
|
left: 0;
|
||||||
width: categoryTitleHeight,
|
width: ${categoryTitleHeight}px;
|
||||||
position: 'absolute',
|
position: absolute;
|
||||||
writingMode: 'vertical-rl',
|
writing-mode: vertical-rl;
|
||||||
transform: 'rotate(180deg)',
|
transform: rotate(180deg);
|
||||||
textAlign: 'center',
|
text-align: center;
|
||||||
display: 'flex',
|
display: flex;
|
||||||
alignItems: 'center',
|
align-items: center;
|
||||||
justifyContent: 'center'
|
justify-content: center;
|
||||||
}}>
|
">
|
||||||
<CategoryHeader href={href} label={header} guideAnchor={guideInfo} background={color} rotate={true} />
|
${renderCategoryHeader({href, label: header, guideAnchor: guideInfo, background: color,rotate: true})}
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style="
|
||||||
marginLeft: 30,
|
margin-left: 30px;
|
||||||
height: '100%',
|
height: 100%;
|
||||||
display: 'flex',
|
display: flex;
|
||||||
justifyContent: 'space-evenly',
|
justify-content: space-evenly;
|
||||||
background: 'white'
|
background: white;
|
||||||
}}>
|
">
|
||||||
{subcategoriesWithCalculations.map((subcategory, index) => {
|
${subcategoriesWithCalculations.map((subcategory, index) => {
|
||||||
const lastSubcategory = index !== subcategories.length - 1
|
const lastSubcategory = index !== subcategories.length - 1
|
||||||
const { allItems, guideInfo, columns, width, name, href } = subcategory
|
const { allItems, guideInfo, columns, width, name, href } = subcategory
|
||||||
const padding = fitWidth ? 0 : `${subcategoryMargin}px 0`
|
const padding = fitWidth ? 0 : `${subcategoryMargin}px 0`;
|
||||||
const style = {
|
const style = `
|
||||||
display: 'grid',
|
display: grid;
|
||||||
height: '100%',
|
height: 100%;
|
||||||
gridTemplateColumns: `repeat(${columns}, ${smallItemWidth}px)`,
|
grid-template-columns: repeat(${columns}, ${smallItemWidth}px);
|
||||||
gridAutoRows: `${smallItemHeight}px`
|
grid-auto-rows: ${smallItemHeight}px;
|
||||||
}
|
`;
|
||||||
const extraStyle = fitWidth ? { justifyContent: 'space-evenly', alignContent: 'space-evenly' } : { gridGap: itemMargin }
|
const extraStyle = fitWidth ? `justify-content: space-evenly; align-content: space-evenly;` : `grid-gap: ${itemMargin}px;`;
|
||||||
const path = [header, name].join(' / ')
|
return `
|
||||||
|
<div style="
|
||||||
return <Fragment key={name}>
|
width: ${width}px;
|
||||||
<div style={{
|
position: relative;
|
||||||
width,
|
overflow: visible;
|
||||||
position: 'relative',
|
padding: ${padding};
|
||||||
overflow: 'visible',
|
box-sizing: border-box;
|
||||||
padding,
|
">
|
||||||
boxSizing: 'border-box'
|
<div style="
|
||||||
}}>
|
position: absolute;
|
||||||
<div style={{
|
top: ${-1 * categoryTitleHeight}px;
|
||||||
position: 'absolute',
|
left: 0;
|
||||||
top: -1 * categoryTitleHeight,
|
right: 0;
|
||||||
left: 0,
|
height: ${categoryTitleHeight}px;
|
||||||
right: 0,
|
display: flex;
|
||||||
height: categoryTitleHeight,
|
align-items: center;
|
||||||
display: 'flex',
|
justify-content: center;
|
||||||
alignItems: 'center',
|
text-align: center;
|
||||||
justifyContent: 'center',
|
">
|
||||||
textAlign: 'center'
|
<a data-type="internal" href="${href}" class="white-link">${h(name)}</a>
|
||||||
}}>
|
</div>
|
||||||
<InternalLink to={href} className="white-link">{name}</InternalLink>
|
<div style="${style} ${extraStyle}">
|
||||||
|
${allItems.map(renderItem).join('')}
|
||||||
|
${guideInfo ? renderSubcategoryInfo({label: name, anchor: guideInfo,column: columns, row:totalRows}) : ''}
|
||||||
</div>
|
</div>
|
||||||
<div style={{...style, ...extraStyle}}>
|
|
||||||
{
|
|
||||||
allItems.map(item => <Item item={item} key={item.name}/>)
|
|
||||||
}
|
|
||||||
|
|
||||||
{ guideInfo && <SubcategoryInfo label={name} anchor={guideInfo} column={columns} row={totalRows}/> } </div>
|
|
||||||
</div>
|
</div>
|
||||||
|
${lastSubcategory ? renderDivider(color) : ''}
|
||||||
{lastSubcategory && <Divider color={color}/>}
|
`
|
||||||
</Fragment>
|
}).join('')}
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HorizontalCategory
|
|
||||||
|
|
|
@ -1,64 +1,78 @@
|
||||||
import path from 'path';
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import fs from 'fs';
|
const { fields } = require("../types/fields");
|
||||||
import settings from 'dist/settings';
|
const { h } = require('../utils/format');
|
||||||
import fields from "../types/fields";
|
|
||||||
import {
|
|
||||||
largeItemHeight,
|
|
||||||
largeItemWidth,
|
|
||||||
smallItemHeight,
|
|
||||||
smallItemWidth
|
|
||||||
} from "../utils/landscapeCalculations";
|
|
||||||
import assetPath from '../utils/assetPath'
|
|
||||||
|
|
||||||
const LargeItem = ({ item, onClick }) => {
|
const { readJsonFromDist } = require('../utils/readJson');
|
||||||
|
const settings = readJsonFromDist('settings');
|
||||||
|
|
||||||
|
const largeItem = function(item) {
|
||||||
|
const isMember = item.category === settings.global.membership;
|
||||||
const relationInfo = fields.relation.valuesMap[item.relation]
|
const relationInfo = fields.relation.valuesMap[item.relation]
|
||||||
|
if (!relationInfo) {
|
||||||
|
console.error(`no map for ${item.relation} on ${item.name}`);
|
||||||
|
}
|
||||||
const color = relationInfo.big_picture_color;
|
const color = relationInfo.big_picture_color;
|
||||||
const label = relationInfo.big_picture_label;
|
const label = relationInfo.big_picture_label;
|
||||||
const textHeight = label ? 10 : 0
|
const textHeight = label ? 10 : 0
|
||||||
const padding = 2
|
const padding = 2
|
||||||
|
|
||||||
return <div data-id={item.id} className="large-item item" onClick={onClick} style={{ background: color }}>
|
const isMultiline = h(label).length > 20;
|
||||||
|
const formattedLabel = isMultiline ? h(label).replace(' - ', '<br>') : h(label);
|
||||||
|
|
||||||
<img loading="lazy" src={assetPath(item.href)} alt={item.name} style={{
|
if (isMember) {
|
||||||
width: `calc(100% - ${2 * padding}px)`,
|
return `
|
||||||
height: `calc(100% - ${2 * padding + textHeight}px)`,
|
<div data-id="${item.id}" class="large-item large-item-${item.size} item">
|
||||||
padding: 5,
|
<img loading="lazy" src="${assetPath(item.href)}" alt="${item.name}" style="
|
||||||
margin: `${padding}px ${padding}px 0 ${padding}px`
|
width: calc(100% - ${2 * padding}px);
|
||||||
}}/>
|
height: calc(100% - ${2 * padding + textHeight}px);
|
||||||
<div className="label" style={{
|
padding: 5px;
|
||||||
position: 'absolute',
|
margin: ${padding}px ${padding}px 0 ${padding}px;
|
||||||
bottom: 0,
|
"/>
|
||||||
width: '100%',
|
</div>`;
|
||||||
height: `${textHeight + padding}px`,
|
}
|
||||||
textAlign: 'center',
|
|
||||||
verticalAlign: 'middle',
|
return `
|
||||||
background: color,
|
<div data-id="${item.id}" class="large-item large-item-${item.size} item" style="background: ${color}">
|
||||||
color: 'white',
|
<img loading="lazy" src="${assetPath(item.href)}" alt="${item.name}" style="
|
||||||
fontSize: '6.7px',
|
width: calc(100% - ${2 * padding}px);
|
||||||
lineHeight: '13px'
|
height: calc(100% - ${2 * padding + textHeight}px);
|
||||||
}}>{label}</div>
|
padding: 5px;
|
||||||
</div>;
|
margin: ${padding}px ${padding}px 0 ${padding}px;
|
||||||
|
"/>
|
||||||
|
<div class="label" style="
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: ${textHeight + padding + (isMultiline ? 6 : 0) }px;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
background: ${color};
|
||||||
|
color: white;
|
||||||
|
font-size: 6.7px;
|
||||||
|
line-height: ${isMultiline ? 9 : 13 }px;
|
||||||
|
">${ formattedLabel }</div>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SmallItem = ({ item, onClick }) => {
|
const smallItem = function(item) {
|
||||||
const isMember = item.category === settings.global.membership;
|
const isMember = item.category === settings.global.membership;
|
||||||
return <>
|
return `
|
||||||
<img data-id={item.id} loading="lazy" className="item small-item" src={assetPath(item.href)} onClick={onClick} alt={item.name} style={{
|
<img data-id="${item.id}"
|
||||||
borderColor: isMember ? 'white' : undefined
|
loading="lazy"
|
||||||
}}/>
|
class="item small-item"
|
||||||
</>
|
src="${assetPath(item.href)}"
|
||||||
|
alt="${h(item.name)}"
|
||||||
|
style="border-color: ${isMember ? 'white' : ''};"
|
||||||
|
/>`
|
||||||
}
|
}
|
||||||
|
|
||||||
const Item = props => {
|
module.exports.renderItem = function (item) {
|
||||||
const { isLarge, category, oss, categoryAttrs } = props.item
|
const {size, category, oss, categoryAttrs } = item;
|
||||||
const isMember = category === settings.global.membership;
|
const isMember = category === settings.global.membership;
|
||||||
|
const ossClass = isMember || oss || (categoryAttrs.isLarge && !settings.global.flags?.gray_large_items) ? 'oss' : 'nonoss';
|
||||||
|
const isLargeClass = size > 1 ? `wrapper-large-${size}` : '';
|
||||||
|
|
||||||
const ossClass = isMember || oss || categoryAttrs.isLarge ? 'oss' : 'nonoss';
|
return `<div class="${isLargeClass + ' item-wrapper ' + ossClass}">
|
||||||
const isLargeClass = isLarge ? 'wrapper-large' : '';
|
${size > 1 ? largeItem({isMember, ...item}) : smallItem({...item})}
|
||||||
|
</div>`;
|
||||||
return <div className={isLargeClass + ' item-wrapper ' + ossClass}>
|
|
||||||
{isLarge ? <LargeItem {...props} isMember={isMember} /> : <SmallItem {...props} />}
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Item
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import React, { Fragment } from 'react';
|
const _ = require('lodash');
|
||||||
import ReactDOMServer from 'react-dom/server';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
// Render all items here!
|
// Render all items here!
|
||||||
|
|
||||||
import HorizontalCategory from './HorizontalCategory'
|
const { renderHorizontalCategory } = require('./HorizontalCategory');
|
||||||
import VerticalCategory from './VerticalCategory'
|
const { renderVerticalCategory } = require('./VerticalCategory');
|
||||||
import LandscapeInfo from './LandscapeInfo';
|
const { renderLandscapeInfo } = require('./LandscapeInfo');
|
||||||
import OtherLandscapeLink from './OtherLandscapeLink';
|
const { renderOtherLandscapeLink } = require('./OtherLandscapeLink');
|
||||||
|
|
||||||
const extractKeys = (obj, keys) => {
|
const extractKeys = (obj, keys) => {
|
||||||
const attributes = _.pick(obj, keys)
|
const attributes = _.pick(obj, keys)
|
||||||
|
@ -16,34 +14,32 @@ const extractKeys = (obj, keys) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getElement({landscapeSettings, landscapeItems}) {
|
module.exports.render = function({landscapeSettings, landscapeItems}) {
|
||||||
const elements = landscapeSettings.elements.map(element => {
|
const elements = landscapeSettings.elements.map(element => {
|
||||||
if (element.type === 'LandscapeLink') {
|
if (element.type === 'LandscapeLink') {
|
||||||
return <OtherLandscapeLink {..._.pick(element, ['width','height','top','left','color', 'layout', 'title', 'url', 'image']) }
|
return renderOtherLandscapeLink(element)
|
||||||
key={JSON.stringify(element)}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
if (element.type === 'LandscapeInfo') {
|
if (element.type === 'LandscapeInfo') {
|
||||||
return <LandscapeInfo {..._.pick(element, ['width', 'height', 'top', 'left']) } childrenInfo={element.children}
|
return renderLandscapeInfo(element)
|
||||||
key={JSON.stringify(element)}
|
}
|
||||||
/>
|
const category = landscapeItems.find(c => c.key === element.category);
|
||||||
|
if (!category) {
|
||||||
|
console.info(`Can not find the ${element.category}`);
|
||||||
|
console.info(`Valid values: ${landscapeItems.map( (x) => x.key).join('; ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const category = landscapeItems.find(c => c.key === element.category) || {}
|
|
||||||
const attributes = extractKeys(element, ['width', 'height', 'top', 'left', 'color', 'fit_width', 'is_large'])
|
const attributes = extractKeys(element, ['width', 'height', 'top', 'left', 'color', 'fit_width', 'is_large'])
|
||||||
const subcategories = category.subcategories.map(subcategory => {
|
const subcategories = category.subcategories.map(subcategory => {
|
||||||
const allItems = subcategory.allItems.map(item => ({ ...item, categoryAttrs: attributes }))
|
const allItems = subcategory.allItems.map(item => ({ ...item, categoryAttrs: attributes }))
|
||||||
return { ...subcategory, allItems }
|
return { ...subcategory, allItems }
|
||||||
})
|
})
|
||||||
|
|
||||||
const Component = element.type === 'HorizontalCategory' ? HorizontalCategory : VerticalCategory
|
if (element.type === 'HorizontalCategory') {
|
||||||
return <Component {...category} subcategories={subcategories} {...attributes} />
|
return renderHorizontalCategory({...category, ...attributes, subcategories: subcategories});
|
||||||
});
|
}
|
||||||
|
if (element.type === 'VerticalCategory') {
|
||||||
|
return renderVerticalCategory({...category, ...attributes, subcategories: subcategories});
|
||||||
|
}
|
||||||
|
}).join('');
|
||||||
|
|
||||||
return <div style={{ position: 'relative' }}>
|
return `<div style="position: relative;">${elements}</div>`;
|
||||||
{elements}
|
|
||||||
</div>
|
|
||||||
};
|
};
|
||||||
export function render() {
|
|
||||||
return ReactDOMServer.renderToStaticMarkup(getElement.apply(this, arguments));
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,74 +1,56 @@
|
||||||
import React from 'react';
|
const { h } = require('../utils/format');
|
||||||
import _ from 'lodash';
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import assetPath from '../utils/assetPath';
|
|
||||||
|
|
||||||
const LandscapeInfo = ({width, height, top, left, childrenInfo}) => {
|
module.exports.renderLandscapeInfo = function({width, height, top, left, children}) {
|
||||||
const children = childrenInfo.map(function(info) {
|
children = children.map(function(info) {
|
||||||
const positionProps = {
|
const positionStyle = `
|
||||||
position: 'absolute',
|
position: absolute;
|
||||||
top: _.isUndefined(info.top) ? null : info.top,
|
top: ${info.top}px;
|
||||||
left: _.isUndefined(info.left) ? null : info.left,
|
left: ${info.left}px;
|
||||||
right: _.isUndefined(info.right) ? null : info.right,
|
right: ${info.right}px;
|
||||||
bottom: _.isUndefined(info.bottom) ? null : info.bottom,
|
bottom: ${info.bottom}px;
|
||||||
width: _.isUndefined(info.width) ? null : info.width,
|
width: ${info.width}px;
|
||||||
height: _.isUndefined(info.height) ? null : info.height
|
height: ${info.height}px;
|
||||||
};
|
`;
|
||||||
if (info.type === 'text') {
|
if (info.type === 'text') {
|
||||||
// pdf requires a normal version without a zoom trick
|
return `<div key='text' style="
|
||||||
const isPdf = false;
|
${positionStyle}
|
||||||
if (isPdf) {
|
font-size: ${info.font_size * 4}px;
|
||||||
return <div key='text' style={{
|
font-style: italic;
|
||||||
...positionProps,
|
text-align: justify;
|
||||||
fontSize: info.font_size,
|
z-index: 1;
|
||||||
fontStyle: 'italic',
|
"><div style="
|
||||||
textAlign: 'justify',
|
position: absolute;
|
||||||
zIndex: 1
|
left: 0;
|
||||||
}}>{info.text}</div>
|
top: 0;
|
||||||
// while in a browser we use a special version which renders fonts
|
width: 400%;
|
||||||
// properly on a small zoom
|
height: 100%;
|
||||||
} else {
|
transform: scale(0.25);
|
||||||
return <div key='text' style={{
|
transform-origin: left;
|
||||||
...positionProps,
|
"> ${h(info.text)} </div></div>`;
|
||||||
fontSize: info.font_size * 4,
|
|
||||||
fontStyle: 'italic',
|
|
||||||
textAlign: 'justify',
|
|
||||||
zIndex: 1
|
|
||||||
}}><div style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
width: '400%',
|
|
||||||
height: '100%',
|
|
||||||
transform: 'scale(0.25)',
|
|
||||||
transformOrigin: 'left'
|
|
||||||
}}> {info.text} </div></div>
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (info.type === 'title') {
|
if (info.type === 'title') {
|
||||||
return <div key='title' style= {{
|
return `<div key='title' style="
|
||||||
...positionProps,
|
${positionStyle}
|
||||||
fontSize: info.font_size,
|
font-size: ${info.font_size}px;
|
||||||
color: '#666'
|
color: #666;
|
||||||
}}>{info.title}</div>
|
">${h(info.title)}</div>`;
|
||||||
}
|
}
|
||||||
if (info.type === 'image') {
|
if (info.type === 'image') {
|
||||||
return <img src={assetPath(`images/${info.image}`)} style={{...positionProps}} key={info.image} alt={info.title || info.image} />
|
return `<img src="${assetPath(`images/${info.image}`)}" style="${positionStyle}" alt="${info.title || info.image}" />`;
|
||||||
}
|
}
|
||||||
});
|
}).join('');
|
||||||
|
|
||||||
return <div style={{
|
return `<div style="
|
||||||
position: 'absolute',
|
position: absolute;
|
||||||
width: width,
|
width: ${width}px;
|
||||||
height: height - 20,
|
height: ${height - 20}px;
|
||||||
top: top,
|
top: ${top}px;
|
||||||
left: left,
|
left: ${left}px;
|
||||||
border: '1px solid black',
|
border: 1px solid black;
|
||||||
background: 'white',
|
background: white;
|
||||||
borderRadius: 10,
|
border-radius: 10px;
|
||||||
marginTop: 20,
|
margin-top: 20px;
|
||||||
boxShadow: `0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)`
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
}}>{children}</div>
|
">${children}</div>`
|
||||||
}
|
}
|
||||||
export default LandscapeInfo;
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const { h } = require('../utils/format');
|
||||||
|
const l = function(x) {
|
||||||
|
return h((x || "").replace("https://", ""));
|
||||||
|
}
|
||||||
|
const { formatNumber } = require('../utils/formatNumber');
|
||||||
|
function highlightLinks(s) {
|
||||||
|
if (!s) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
// markdown styles
|
||||||
|
s = s.replace(/\[(.*?)\]\((https?:.*?)\)/g, '<a target="_blank" href="$2">$1</a>')
|
||||||
|
s = s.replace(/(\s|^)(https?:.*?)(\s|$)/g, ' <a target="_blank" href="$2">$2</a> ')
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
const getDate = function(date) {
|
||||||
|
if (!date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return new Date(date).toISOString().substring(0, 10);
|
||||||
|
}
|
||||||
|
const today = getDate(new Date());
|
||||||
|
|
||||||
|
module.exports.render = function({items}) {
|
||||||
|
console.info(items[0], items[0].latestCommitDate);
|
||||||
|
|
||||||
|
const old = _.orderBy( items.filter( (x) => x.latestCommitDate && new Date(x.latestCommitDate).getTime() + 3 * 30 * 86400 * 1000 < new Date().getTime()), 'latestCommitDate');
|
||||||
|
|
||||||
|
console.info(old.map( (x) => x.name));
|
||||||
|
|
||||||
|
return `
|
||||||
|
<head>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>List of obsolete items</h1>
|
||||||
|
${old.map(function(item) {
|
||||||
|
return `
|
||||||
|
<div style="display: flex; flex-direction: row;">
|
||||||
|
<div style="width: 200px; overflow: hidden; padding: 5px;">
|
||||||
|
<img src="logos/${item.image_data.fileName}"></img>
|
||||||
|
</div>
|
||||||
|
<div style="width: 300px; overflow: hidden; padding: 5px;">
|
||||||
|
<h3>${item.name}</h1>
|
||||||
|
<h3>${item.path}</h2>
|
||||||
|
<h3><span><b>Latest Commit: </b></span> ${getDate(item.latestCommitDate)}</h3>
|
||||||
|
<h3><span><b>Repo: </b></span> ${highlightLinks(item.repo_url)}</h3>
|
||||||
|
</div>
|
||||||
|
<div style="width: 600px; padding: 5px;">
|
||||||
|
<h4>Manual Actions</h4>
|
||||||
|
<pre>
|
||||||
|
1. Create an issue in their repo, inform that that a repo is scheduled for deletion.
|
||||||
|
2. Mark this item in a <b>landscape.yml</b> with an <b>extra</b> property <b>obsolete_since</b> equal to ${today}
|
||||||
|
3. After a month remove the entry from the repo
|
||||||
|
===
|
||||||
|
Issue Template:
|
||||||
|
Title: ${item.name} is going to be unreferenced from the interactive landscape because of no activity since ${getDate(item.latestCommitDate)}
|
||||||
|
Body:
|
||||||
|
<div style="font-size: 8px;">
|
||||||
|
Dear project maintainers of ${item.name},
|
||||||
|
|
||||||
|
I hope this message finds you well.
|
||||||
|
I noticed that your project has had no activity for the last 3 months and is about to be removed from the interactive landscape https://landscape.cncf.io/?selected=${item.id}
|
||||||
|
As a maintainer of an interactive landscape, we have included your project as a reference for our users and would like to ensure that the information we provide is up-to-date.
|
||||||
|
We understand that maintaining a project can be challenging and time-consuming, and we would like to offer any assistance that we can. Please let us know if there are any plans to continue the development of the project or if there is anything we can do to help.
|
||||||
|
Thank you for your time and efforts in creating and maintaining this project. We appreciate the value it has provided to the community and hope to continue to reference it in our interactive landscape.
|
||||||
|
Best regards, CNCF Landscape Team
|
||||||
|
</div>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('<hr>')}
|
||||||
|
</body>
|
||||||
|
`
|
||||||
|
}
|
|
@ -1,78 +1,97 @@
|
||||||
import React from 'react';
|
const { assetPath } = require('../utils/assetPath');
|
||||||
import assetPath from '../utils/assetPath';
|
const { h } = require('../utils/format');
|
||||||
|
|
||||||
const OutboundLink = ({to, className, children}) =>
|
const { stringifyParams } = require('../utils/routing');
|
||||||
(<a data-type="external" href={to} className={className}>{children}</a>)
|
const { categoryBorder, categoryTitleHeight, subcategoryTitleHeight } = require('../utils/landscapeCalculations');
|
||||||
const InternalLink = ({to, className, children}) =>
|
|
||||||
(<a data-type="tab" href={to} className={className}>{children}</a>)
|
|
||||||
|
|
||||||
import { stringifyParams } from '../utils/routing'
|
const renderCardLink = ({ url, children }) => {
|
||||||
import { categoryBorder, categoryTitleHeight, subcategoryTitleHeight } from '../utils/landscapeCalculations'
|
if (url.indexOf('http') === 0) {
|
||||||
|
return `<a data-type=external target=_blank href="${url}" style="display: flex; flex-direction: column;">${children}</a>`;
|
||||||
|
} else {
|
||||||
|
url = stringifyParams({ mainContentMode: url });
|
||||||
|
return `<a data-type=tab href="${url}" style="display: flex; flex-direction: column;">${children}</a>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const CardLink = ({ url, children }) => {
|
module.exports.renderOtherLandscapeLink = function({top, left, height, width, color, title, image, url, layout}) {
|
||||||
const Component = url.indexOf('http') === 0 ? OutboundLink : InternalLink
|
title = title || ''; //avoid undefined!
|
||||||
const to = url.indexOf('http') === 0 ? url : stringifyParams({ mainContentMode: url })
|
|
||||||
|
|
||||||
return <Component to={to} style={{ display: 'flex', flexDirection: 'column' }}>{children}</Component>
|
|
||||||
}
|
|
||||||
|
|
||||||
const OtherLandscapeLink = function({top, left, height, width, color, title, image, url, layout}) {
|
|
||||||
const imageSrc = image || assetPath(`images/${url}_preview.png`);
|
const imageSrc = image || assetPath(`images/${url}_preview.png`);
|
||||||
if (layout === 'category') {
|
if (layout === 'category') {
|
||||||
return <div style={{
|
return `<div style="
|
||||||
position: 'absolute', top, left, height, width, background: color,
|
position: absolute;
|
||||||
overflow: 'hidden',
|
top: ${top}px;
|
||||||
cursor: 'pointer',
|
left: ${left}px;
|
||||||
boxShadow: `0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)`,
|
height: ${height}px;
|
||||||
padding: 1,
|
width: ${width}px;
|
||||||
display: 'flex'
|
background: ${color};
|
||||||
}}>
|
overflow: hidden;
|
||||||
<CardLink url={url}>
|
cursor: pointer;
|
||||||
<div style={{ width, height: 30, lineHeight: '28px', textAlign: 'center', color: 'white', fontSize: 12}}>{title}</div>
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
|
||||||
<div style={{ flex: 1, background: 'white', position: 'relative', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
padding: 1px;
|
||||||
<img loading="lazy" src={imageSrc} style={{ width: width - 12, height: height - 42,
|
display: flex;
|
||||||
objectFit: 'contain', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }} alt={title} />
|
">
|
||||||
</div>
|
${renderCardLink({url: url, children: `
|
||||||
</CardLink>
|
<div style="width: ${width}px;height: 30px; line-height: 28px; text-align: center; color: white; font-size: 12px;">${h(title)}</div>
|
||||||
</div>
|
<div style="flex: 1; background: white; position: relative; display: flex; justify-content: center; align-items: center;">
|
||||||
|
<img loading="lazy" src="${imageSrc}" style="
|
||||||
|
width: ${width - 12}px;
|
||||||
|
height: ${height - 42}px;
|
||||||
|
object-fit: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;" alt="${h(title)}" />
|
||||||
|
</div>`})}
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
if (layout === 'subcategory') {
|
if (layout === 'subcategory') {
|
||||||
return <div style={{ width, left, height, top, position: 'absolute', overflow: 'hidden' }}>
|
return `<div style="
|
||||||
<CardLink url={url}>
|
width: ${width}px;
|
||||||
|
left: ${left}px;
|
||||||
|
height: ${height}px;
|
||||||
|
top: ${top}px;
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;">
|
||||||
|
${renderCardLink({url: url, children: `
|
||||||
<div
|
<div
|
||||||
style={{
|
style="
|
||||||
position: 'absolute',
|
position: absolute;
|
||||||
background: color,
|
background: ${color};
|
||||||
top: subcategoryTitleHeight,
|
top: ${subcategoryTitleHeight}px;
|
||||||
bottom: 0,
|
bottom: 0;
|
||||||
left: 0,
|
left: 0;
|
||||||
right: 0,
|
right: 0;
|
||||||
boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2)',
|
boxShadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);
|
||||||
padding: categoryBorder,
|
padding: ${categoryBorder}px;
|
||||||
display: 'flex'
|
display: flex;
|
||||||
}}
|
"
|
||||||
>
|
>
|
||||||
<div style={{
|
<div style="
|
||||||
width: categoryTitleHeight,
|
width: ${categoryTitleHeight}px;
|
||||||
writingMode: 'vertical-rl',
|
writing-mode: vertical-rl;
|
||||||
transform: 'rotate(180deg)',
|
transform: rotate(180deg);
|
||||||
textAlign: 'center',
|
text-align: center;
|
||||||
display: 'flex',
|
display: flex;
|
||||||
alignItems: 'center',
|
align-items: center;
|
||||||
justifyContent: 'center',
|
justify-content: center;
|
||||||
fontSize: 12,
|
font-size: 12px;
|
||||||
lineHeight: '13px',
|
line-height: 13px;
|
||||||
color: 'white'
|
color: white;
|
||||||
}}>
|
">
|
||||||
{title}
|
${h(title)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', flex: 1, background: 'white', justifyContent: 'center', alignItems: 'center' }}>
|
<div style="
|
||||||
<img loading="lazy" src={imageSrc} alt={title}
|
display: flex;
|
||||||
style={{ width: width - 42, height: height - 32, objectFit: 'contain', backgroundPosition: 'center', backgroundRepeat: 'no-repeat' }} />
|
flex: 1;
|
||||||
|
background: white;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center; ">
|
||||||
|
<img loading="lazy" src="${imageSrc}" alt="${h(title)}"
|
||||||
|
style="width: ${width - 42}px;
|
||||||
|
height: ${height - 32}px;
|
||||||
|
object-fit: contain;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`})}
|
||||||
</CardLink>
|
</div>`
|
||||||
</div>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default OtherLandscapeLink;
|
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import GuideLink from './GuideLink'
|
const { renderGuideLink } = require('./GuideLink');
|
||||||
import { smallItemHeight, smallItemWidth } from '../utils/landscapeCalculations'
|
const { smallItemHeight, smallItemWidth } = require('../utils/landscapeCalculations');
|
||||||
|
|
||||||
|
module.exports.renderSubcategoryInfo = function({ label, anchor, row, column }) {
|
||||||
const SubcategoryInfo = ({ label, anchor, row, column }) => {
|
const style=`
|
||||||
const style={
|
width: ${smallItemWidth}px;
|
||||||
width: smallItemWidth,
|
height: ${smallItemHeight}px;
|
||||||
height: smallItemHeight,
|
display: flex;
|
||||||
display: 'flex',
|
align-items: center;
|
||||||
alignItems: 'center',
|
justify-content: center;
|
||||||
justifyContent: 'center',
|
font-size: 18px;
|
||||||
fontSize: '18px',
|
grid-column-start: ${column || 'auto'};
|
||||||
gridColumnStart: column || 'auto',
|
grid-row-start: ${row || 'auto'};
|
||||||
gridRowStart: row || 'auto'
|
`;
|
||||||
};
|
return renderGuideLink({label: label, anchor: anchor, style: style})
|
||||||
return <GuideLink label={label} anchor={anchor} style={style}/>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SubcategoryInfo;
|
|
||||||
|
|
|
@ -0,0 +1,624 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const { h } = require('../utils/format');
|
||||||
|
const l = function(x) {
|
||||||
|
return h((x || "").replace("https://", ""));
|
||||||
|
}
|
||||||
|
const { formatNumber } = require('../utils/formatNumber');
|
||||||
|
|
||||||
|
const getLanguages = function(item) {
|
||||||
|
if (item.extra && item.extra.summary_languages) {
|
||||||
|
return item.extra.summary_languages;
|
||||||
|
}
|
||||||
|
if (item.github_data && item.github_data.languages) {
|
||||||
|
const total = _.sum(item.github_data.languages.map( (x) => x.value));
|
||||||
|
const matching = item.github_data.languages.filter( (x) => x.value > total * 0.3).map( (x) => x.name);
|
||||||
|
return matching.join(', ');
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightLinks(s) {
|
||||||
|
if (!s) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
// markdown styles
|
||||||
|
s = s.replace(/\[(.*?)\]\((https?:.*?)\)/g, '<a target="_blank" href="$2">$1</a>')
|
||||||
|
s = s.replace(/(\s|^)(https?:.*?)(\s|$)/g, ' <a target="_blank" href="$2">$2</a> ')
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDate = function(date) {
|
||||||
|
if (!date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return new Date(date).toISOString().substring(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.render = function({items}) {
|
||||||
|
|
||||||
|
const projects = items.filter( (x) => !!x.relation && x.relation !== 'member');
|
||||||
|
const categories = _.uniq(projects.map( (x) => x.path.split(' / ')[0]));
|
||||||
|
const categoriesCount = {};
|
||||||
|
const categoryItems = {};
|
||||||
|
const subcategories = {};
|
||||||
|
for (let k of categories) {
|
||||||
|
categoriesCount[k] = projects.filter( (x) => x.path.split(' / ')[0] === k).length;
|
||||||
|
categoryItems[k] = projects.filter( (x) => x.path.split(' / ')[0] === k).map( (x) => projects.indexOf(x));
|
||||||
|
const arr = _.uniq(projects.filter( (x) => x.path.split(' / ')[0] === k).map( (x) => x.path.split(' / ')[1]));
|
||||||
|
for (let subcategory of arr) {
|
||||||
|
categoryItems[k + ':' + subcategory] = projects.filter( (x) => x.path === k + ' / ' + subcategory).map( (x) => projects.indexOf(x));
|
||||||
|
}
|
||||||
|
subcategories[k] = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const columnWidth = 250;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<head>
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
|
<style>
|
||||||
|
${require('fs').readFileSync('src/fonts.css', 'utf-8')}
|
||||||
|
::root {
|
||||||
|
--navy: #38404a;
|
||||||
|
--navy-light: #696D70;
|
||||||
|
--blue: #2E67BF;
|
||||||
|
--blue-hover: #1D456B;
|
||||||
|
--spacing: 1em;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.43;
|
||||||
|
letter-spacing: 0.01071em;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
td a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #2E67BF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.categories {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subcategories {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: ${columnWidth * projects.length}px;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-top: 1px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#headers {
|
||||||
|
width: 152px;
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#data {
|
||||||
|
position: absolute;
|
||||||
|
left: 155px;
|
||||||
|
top: 0px;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
width: ${columnWidth}px;
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid white;
|
||||||
|
border-top-width: 0px;
|
||||||
|
height: 53px;
|
||||||
|
padding: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: var(--navy);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-name {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
top: 165px;
|
||||||
|
width: calc(100% - 16px);
|
||||||
|
bottom: 0;
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: scroll;
|
||||||
|
padding: 0;
|
||||||
|
left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: relative;
|
||||||
|
background-color: #1b446c;
|
||||||
|
color: white;
|
||||||
|
width: 152px;
|
||||||
|
top: auto;
|
||||||
|
font-size: 0.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
.first-line {
|
||||||
|
background-color: #1b446c;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.alternate-line {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
.sticky span {
|
||||||
|
white-space: nowrap;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.1rem;
|
||||||
|
line-height: 30px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
color: var(--navy);
|
||||||
|
}
|
||||||
|
.landscape-logo {
|
||||||
|
width: 160px;
|
||||||
|
height: 48px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.landscapeapp-logo {
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 14px;
|
||||||
|
}
|
||||||
|
.landscapeapp-logo img {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.main-header {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* select starting stylings ------------------------------*/
|
||||||
|
.select {
|
||||||
|
font-family: 'Roboto';
|
||||||
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.select-disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-text {
|
||||||
|
position: relative;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 10px 22px 10px 0;
|
||||||
|
border-radius: 0;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remove focus */
|
||||||
|
.select-text:focus {
|
||||||
|
outline: none;
|
||||||
|
border-bottom: 1px solid rgba(0,0,0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use custom arrow */
|
||||||
|
.select .select-text {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:after {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 10px;
|
||||||
|
/* Styling the down arrow */
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
padding: 0;
|
||||||
|
content: '';
|
||||||
|
border-left: 6px solid transparent;
|
||||||
|
border-right: 6px solid transparent;
|
||||||
|
border-top: 6px solid rgba(0, 0, 0, 0.12);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* LABEL ======================================= */
|
||||||
|
.select-label {
|
||||||
|
color: rgb(105, 109, 112);
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: normal;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 0;
|
||||||
|
top: 10px;
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active state */
|
||||||
|
.select-text:focus ~ .select-label, .select-text ~ .select-label {
|
||||||
|
top: -10px;
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* BOTTOM BARS ================================= */
|
||||||
|
.select-bar {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:before, .select-bar:after {
|
||||||
|
content: '';
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
bottom: 1px;
|
||||||
|
position: absolute;
|
||||||
|
background: #2F80ED;
|
||||||
|
transition: 0.2s ease all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:before {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-bar:after {
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active state */
|
||||||
|
.select-text:focus ~ .select-bar:before, .select-text:focus ~ .select-bar:after {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-highlight {
|
||||||
|
position: absolute;
|
||||||
|
height: 60%;
|
||||||
|
width: 100px;
|
||||||
|
top: 25%;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 540px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main-header">
|
||||||
|
<span class="landscape-logo">
|
||||||
|
<a aria-label="reset filters" class="nav-link" href="/">
|
||||||
|
<img alt="landscape logo" src="/images/left-logo.svg">
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span style="display: inline-block; position: relative; top: -8px; left: 20px;">
|
||||||
|
<h1>CNCF Project Summary Table</h1>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a rel="noopener noreferrer noopener noreferrer" class="landscapeapp-logo" title="CNCF" target="_blank" href="https://www.cncf.io">
|
||||||
|
<img src="/images/right-logo.svg" title="CNCF">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding: 16px; position: relative; top: -19px;">
|
||||||
|
<div class="categories">
|
||||||
|
<div class="select">
|
||||||
|
<select class="select-text" required="">
|
||||||
|
<option value="" selected="">All: ${projects.length}</option>
|
||||||
|
${categories.map( (name) => `<option value="${name}">${name}: ${categoriesCount[name]}</option>`).join('')}
|
||||||
|
</select>
|
||||||
|
<span class="select-highlight"></span>
|
||||||
|
<span class="select-bar"></span>
|
||||||
|
<label class="select-label">Category</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="subcategories" style="display: none">
|
||||||
|
<div class="select">
|
||||||
|
<select class="select-text" required="">
|
||||||
|
|
||||||
|
</select>
|
||||||
|
<span class="select-highlight"></span>
|
||||||
|
<span class="select-bar"></span>
|
||||||
|
<label class="select-label">Subcategory</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
The <i>CNCF Project Summary Table</i> provides a standardized, summary of CNCF projects.<br/><div style="height: 5px;"></div>
|
||||||
|
<b style="color: rgb(58,132,247);">The filters on the left side help refine your view.</b> Start by filtering by category (e.g., <i>orchestration and management</i>) and then subcategory (e.g., <i>service mesh</i> for an overview of all available CNCF service meshes).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table id="headers">
|
||||||
|
<tr class="landscape first-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span> Project </span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Description</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Maturity</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Target Users</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Tags</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Use Case</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Business Use</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Languages</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>First Commit</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Last Commit</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Release Cadence</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Github Stars</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Integrations</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Website</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Github</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
<td class="sticky">
|
||||||
|
<span>Overview Video</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table id="data">
|
||||||
|
<tr class="landscape first-line">
|
||||||
|
${projects.map( (project, index) => `
|
||||||
|
<td class="project-name" data-project-index="${index}">${h(project.name)}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.github_data || project)['description'])}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h(project.relation)}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.extra || {})['summary_personas']) || ' '}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.extra || {})['summary_tags'] || '').split(',').map( (tag) => `<div>- ${tag.trim()}</div>`).join('') }</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.extra || {})['summary_use_case']) || ' '}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.extra || {})['summary_business_use_case']) || ' '}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h(getLanguages(project))}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h(getDate((project.github_start_commit_data || {}).start_date))}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h(getDate((project.github_data || {}).latest_commit_date))}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h((project.extra || {})['summary_release_rate']) || ' '}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${h(formatNumber((project.github_data || {}).stars))}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td>${highlightLinks((project.extra || {})['summary_integrations']) || ' '}</td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => `
|
||||||
|
<td><a href="${h((project.homepage_url))}" target="_blank">${l(project.homepage_url)}</a></td>
|
||||||
|
`).join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape alternate-line">
|
||||||
|
${projects.map( (project) => project.repo_url ? `
|
||||||
|
<td><a href="${h(project.repo_url)}" target="_blank">${l(project.repo_url)}</a></td>
|
||||||
|
`: '<td> </td>').join('')}
|
||||||
|
</tr>
|
||||||
|
<tr class="landscape">
|
||||||
|
${projects.map( (project) => project.extra && project.extra.summary_intro_url ? `
|
||||||
|
<td><a href="${h(project.extra.summary_intro_url)}" target="_blank">${l(project.extra.summary_intro_url)}</a></td>
|
||||||
|
`: '<td> </td>').join('')}
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div style="height: 20px;"></div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function setHeight() {
|
||||||
|
const rows = [...document.querySelectorAll('#data tr')];
|
||||||
|
const headersRows = [...document.querySelectorAll('#headers tr')];
|
||||||
|
for (let row of rows) {
|
||||||
|
const index = rows.indexOf(row);
|
||||||
|
const headerEl = headersRows[index].querySelector('td');
|
||||||
|
const firstEl = [...row.querySelectorAll('td')].filter((x) => x.style.display !== 'none')[0];
|
||||||
|
headerEl.style.height = (firstEl.getBoundingClientRect().height) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.App = {
|
||||||
|
totalCount: ${projects.length},
|
||||||
|
categories: ${JSON.stringify(categories)},
|
||||||
|
categoryItems: ${JSON.stringify(categoryItems)},
|
||||||
|
subcategories: ${JSON.stringify(subcategories)}
|
||||||
|
};
|
||||||
|
document.querySelector('.categories select').addEventListener('change', function(e) {
|
||||||
|
const selectedOption = Array.from(document.querySelectorAll('.categories option')).find( (x) => x.selected);
|
||||||
|
const categoryId = selectedOption.value;
|
||||||
|
if (!categoryId) {
|
||||||
|
document.querySelector('#data').style.width = '';
|
||||||
|
document.querySelector('.subcategories').style.display = 'none';
|
||||||
|
} else {
|
||||||
|
document.querySelector('.subcategories').style.display = '';
|
||||||
|
const newWidth = ${columnWidth} * App.categoryItems[categoryId].length;
|
||||||
|
document.querySelector('#data').style.width = newWidth + 'px';
|
||||||
|
|
||||||
|
const subcategories = window.App.subcategories[categoryId];
|
||||||
|
const baseMarkup = '<option value="">All</option>';
|
||||||
|
const markup = subcategories.map( (s) => '<option value="' + s + '">' + s + ': ' + window.App.categoryItems[categoryId + ':' + s].length + '</option>').join('');
|
||||||
|
document.querySelector('.subcategories select').innerHTML = baseMarkup + markup;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let tr of [...document.querySelectorAll('tr')]) {
|
||||||
|
let index = 0;
|
||||||
|
for (let td of [...tr.querySelectorAll('td')].slice(1)) {
|
||||||
|
const isVisible = categoryId ? App.categoryItems[categoryId].includes(index) : true;
|
||||||
|
td.style.display = isVisible ? '' : 'none';
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.subcategories select').addEventListener('change', function(e) {
|
||||||
|
const categoryId = Array.from(document.querySelectorAll('.categories option')).find( (x) => x.selected).value;
|
||||||
|
const subcategoryId = Array.from(document.querySelectorAll('.subcategories option')).find( (x) => x.selected).value;
|
||||||
|
|
||||||
|
let key = subcategoryId ? (categoryId + ':' + subcategoryId) : categoryId;
|
||||||
|
|
||||||
|
const newWidth = ${columnWidth} * App.categoryItems[key].length;
|
||||||
|
document.querySelector('#data').style.width = newWidth + 'px';
|
||||||
|
|
||||||
|
for (let tr of [...document.querySelectorAll('tr')]) {
|
||||||
|
let index = 0;
|
||||||
|
for (let td of [...tr.querySelectorAll('td')].slice(1)) {
|
||||||
|
const isVisible = App.categoryItems[key].includes(index);
|
||||||
|
td.style.display = isVisible ? '' : 'none';
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setHeight();
|
||||||
|
});
|
||||||
|
setHeight();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
|
@ -1,53 +1,66 @@
|
||||||
import React, { useContext } from "react";
|
const { renderItem } = require("./Item");
|
||||||
import Item from "./Item";
|
const { h } = require('../utils/format');
|
||||||
|
|
||||||
const InternalLink = ({to, className, children}) =>
|
const {
|
||||||
(<a data-type="internal" href={to} className={className}>{children}</a>)
|
|
||||||
import {
|
|
||||||
calculateVerticalCategory,
|
calculateVerticalCategory,
|
||||||
categoryTitleHeight,
|
categoryTitleHeight,
|
||||||
itemMargin, smallItemWidth,
|
itemMargin, smallItemWidth,
|
||||||
subcategoryMargin
|
subcategoryMargin
|
||||||
} from "../utils/landscapeCalculations";
|
} = require("../utils/landscapeCalculations");
|
||||||
import SubcategoryInfo from './SubcategoryInfo'
|
const { renderSubcategoryInfo } = require( './SubcategoryInfo');
|
||||||
import CategoryHeader from './CategoryHeader'
|
const { renderCategoryHeader } = require('./CategoryHeader');
|
||||||
|
|
||||||
const VerticalCategory = ({header, guideInfo, subcategories, top, left, width, height, color, href, fitWidth}) => {
|
module.exports.renderVerticalCategory = function({header, guideInfo, subcategories, top, left, width, height, color, href, fitWidth}) {
|
||||||
const subcategoriesWithCalculations = calculateVerticalCategory({ subcategories, fitWidth, width })
|
const subcategoriesWithCalculations = calculateVerticalCategory({ subcategories, fitWidth, width });
|
||||||
|
return `<div>
|
||||||
return <div>
|
<div style="
|
||||||
<div style={{
|
position: absolute;
|
||||||
position: 'absolute', top, left, height, width, background: color,
|
top: ${top}px;
|
||||||
boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2)',
|
left: ${left}px;
|
||||||
padding: 0,
|
height: ${height}px;
|
||||||
display: 'flex',
|
width: ${width}px;
|
||||||
flexDirection: 'column'
|
background: ${color};
|
||||||
}} className="big-picture-section">
|
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.2);
|
||||||
<div style={{ height: categoryTitleHeight, width: '100%', display: 'flex' }}>
|
padding: 0px;
|
||||||
<CategoryHeader href={href} label={header} guideAnchor={guideInfo && guideInfo[header]} background={color} />
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
" class="big-picture-section">
|
||||||
|
<div style="height: ${categoryTitleHeight}px; width: 100%; display: flex;">
|
||||||
|
${renderCategoryHeader({href: href, label: header, guideAnchor: guideInfo, background: color})}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ width: '100%', position: 'relative', flex: 1, padding: `${subcategoryMargin}px 0`, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', background: 'white' }}>
|
<div style="
|
||||||
{subcategoriesWithCalculations.map(subcategory => {
|
width: 100%;
|
||||||
const { guideInfo, width, columns, name } = subcategory
|
position: relative;
|
||||||
const style = { display: 'grid', gridTemplateColumns: `repeat(${columns}, ${smallItemWidth}px)` }
|
flex: 1;
|
||||||
const extraStyle = fitWidth ? { justifyContent: 'space-evenly', flex: 1 } : { gridGap: itemMargin }
|
padding: ${subcategoryMargin}px 0;
|
||||||
const path = [header, name].join(' / ')
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: white
|
||||||
|
">
|
||||||
|
${subcategoriesWithCalculations.map(subcategory => {
|
||||||
|
const { guideInfo, width, columns, name } = subcategory;
|
||||||
|
const style = `
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(${columns}, ${smallItemWidth}px);
|
||||||
|
`;
|
||||||
|
const extraStyle = fitWidth ? `justify-content: space-evenly; flex: 1;` : `grid-gap: ${itemMargin}px; `
|
||||||
|
|
||||||
return <div key={subcategory.name} style={{position: 'relative', flexGrow: subcategory.rows, display: 'flex', flexDirection: 'column' }}>
|
return `<div style="
|
||||||
<div style={{ lineHeight: '15px', textAlign: 'center'}}>
|
position: relative;
|
||||||
<InternalLink to={subcategory.href}>{name}</InternalLink>
|
flex-grow: ${subcategory.rows};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;">
|
||||||
|
<div style="line-height: 15px; text-align: center;">
|
||||||
|
<a data-type=internal href="${subcategory.href}">${h(name)}</a>
|
||||||
|
</div>
|
||||||
|
<div style="width: ${width}px; overflow: hidden; margin: 0 auto; ${style} ${extraStyle}">
|
||||||
|
${subcategory.allItems.map(renderItem).join('')}
|
||||||
|
${guideInfo ? renderSubcategoryInfo({label: name, anchor: guideInfo, column: columns}) : ''}
|
||||||
</div>
|
</div>
|
||||||
|
</div>`
|
||||||
<div style={{width, overflow: 'hidden', margin: '0 auto', ...style, ...extraStyle}}>
|
}).join('')}
|
||||||
{subcategory.allItems.map(item => <Item item={item} key={item.name} fitWidth={fitWidth} />)}
|
|
||||||
|
|
||||||
{ guideInfo && <SubcategoryInfo label={name} anchor={guideInfo} column={columns}/> }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VerticalCategory
|
|
||||||
|
|
|
@ -1,21 +1,27 @@
|
||||||
// An embedded version of a script
|
// An embedded version of a script
|
||||||
const CncfLandscapeApp = {
|
const CncfLandscapeApp = {
|
||||||
init: function() {
|
init: function() {
|
||||||
// get initial state from the url
|
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
document.body.style.height = document.querySelector('.column-content').scrollHeight;
|
window.parent.postMessage({
|
||||||
|
type: 'landscapeapp-resize',
|
||||||
|
height: document.body.scrollHeight
|
||||||
|
}, '*');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
selected: null
|
selected: null
|
||||||
}
|
}
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.keyCode === 27) {
|
if (window.parentIFrame) {
|
||||||
if (CncfLandscapeApp.state.selected) {
|
document.addEventListener('keydown', (e) => {
|
||||||
this.state.selected = null;
|
if (e.keyCode === 27) {
|
||||||
this.hideSelectedItem();
|
if (CncfLandscapeApp.state.selected) {
|
||||||
|
this.state.selected = null;
|
||||||
|
this.hideSelectedItem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
document.body.addEventListener('click', (e) => {
|
document.body.addEventListener('click', (e) => {
|
||||||
const cardEl = e.target.closest('[data-id]');
|
const cardEl = e.target.closest('[data-id]');
|
||||||
|
@ -111,9 +117,21 @@ const CncfLandscapeApp = {
|
||||||
},
|
},
|
||||||
|
|
||||||
showSelectedItem: async function(selectedItemId) {
|
showSelectedItem: async function(selectedItemId) {
|
||||||
|
if (!window.parentIFrame) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'landscapeapp-show',
|
||||||
|
selected: selectedItemId,
|
||||||
|
location: {
|
||||||
|
search: window.location.search,
|
||||||
|
pathname: window.location.pathname
|
||||||
|
}
|
||||||
|
}, '*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedItems = this.selectedItems || {};
|
this.selectedItems = this.selectedItems || {};
|
||||||
if (!this.selectedItems[selectedItemId]) {
|
if (!this.selectedItems[selectedItemId]) {
|
||||||
const result = await fetch(`/data/items/info-${selectedItemId}.html`);
|
const result = await fetch(`${this.basePath}/data/items/info-${selectedItemId}.html`);
|
||||||
const text = await result.text();
|
const text = await result.text();
|
||||||
this.selectedItems[selectedItemId] = text;
|
this.selectedItems[selectedItemId] = text;
|
||||||
}
|
}
|
||||||
|
@ -128,16 +146,12 @@ const CncfLandscapeApp = {
|
||||||
}
|
}
|
||||||
|
|
||||||
//calculate previous and next items;
|
//calculate previous and next items;
|
||||||
let prevItem = null;
|
const selectedItemEl = document.querySelector(`[data-id=${selectedItemId}]`);
|
||||||
let nextItem = null;
|
const parent = selectedItemEl.closest('.cards-section');
|
||||||
if (true) {
|
const allItems = parent.querySelectorAll('[data-id]');
|
||||||
const selectedItemEl = document.querySelector(`[data-id=${selectedItemId}]`);
|
const index = [].indexOf.call(allItems, selectedItemEl);
|
||||||
const parent = selectedItemEl.closest('.cards-section');
|
const prevItem = index > 0 ? allItems[index - 1].getAttribute('data-id') : null;
|
||||||
const allItems = parent.querySelectorAll('[data-id]');
|
const nextItem = index < allItems.length - 1 ? allItems[index + 1].getAttribute('data-id') : null;
|
||||||
const index = [].indexOf.call(allItems, selectedItemEl);
|
|
||||||
prevItem = index > 0 ? allItems[index - 1].getAttribute('data-id') : null;
|
|
||||||
nextItem = index < allItems.length - 1 ? allItems[index + 1].getAttribute('data-id') : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nextItemId = nextItem;
|
this.nextItemId = nextItem;
|
||||||
this.prevItemId = prevItem;
|
this.prevItemId = prevItem;
|
||||||
|
|
|
@ -1,13 +1,31 @@
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
addEventListener('message', function(e) {
|
||||||
iFrameResize({
|
if (e.data && e.data.type === 'landscapeapp-resize') {
|
||||||
log: false,
|
document.querySelector('#landscape').style.height = e.data.height + 'px';
|
||||||
onMessage : function(messageData){ // Callback fn when message is received
|
}
|
||||||
if (messageData.message.type === 'showModal') {
|
if (e.data && e.data.type === 'landscapeapp-show') {
|
||||||
document.querySelector('body').style.overflow = 'hidden';
|
const iframe = document.createElement('iframe');
|
||||||
}
|
document.body.appendChild(iframe);
|
||||||
if (messageData.message.type === 'hideModal') {
|
window.landscapeappModalIframe = iframe;
|
||||||
document.querySelector('body').style.overflow = 'auto';
|
const url = new URL(document.querySelector('#landscape').src);
|
||||||
}
|
const search = url.search || '?a=a';
|
||||||
},
|
let src = url.origin + url.pathname.replace('/pages/', '/pages-modal/') + search + '&selected=' + e.data.selected;
|
||||||
}, '#landscape');
|
if (src.indexOf('/pages-modal') === -1) {
|
||||||
|
//support a version with ?embed=yes
|
||||||
|
src = src + '&showmodal=yes'
|
||||||
|
}
|
||||||
|
iframe.src = src;
|
||||||
|
iframe.style.position = 'fixed';
|
||||||
|
iframe.style.left = 0;
|
||||||
|
iframe.style.top = 0;
|
||||||
|
iframe.style.width = '100%';
|
||||||
|
iframe.style.height = '100%';
|
||||||
|
iframe.style.zIndex = 10000;
|
||||||
|
iframe.focus();
|
||||||
|
}
|
||||||
|
if (e.data && e.data.type === 'landscapeapp-hide') {
|
||||||
|
const iframe = window.landscapeappModalIframe;
|
||||||
|
if (iframe) {
|
||||||
|
document.body.removeChild(iframe);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
// An embedded version of a script
|
||||||
|
const CncfLandscapeApp = {
|
||||||
|
init: function() {
|
||||||
|
// get initial state from the url
|
||||||
|
this.state = {
|
||||||
|
selected: new URLSearchParams(location.search).get('selected')
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
if (CncfLandscapeApp.state.selected) {
|
||||||
|
this.state.selected = null;
|
||||||
|
this.hideSelectedItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener('click', (e) => {
|
||||||
|
const modalBodyEl = e.target.closest('.modal-body');
|
||||||
|
const shadowEl = e.target.closest('.modal-container');
|
||||||
|
if (shadowEl && !modalBodyEl) {
|
||||||
|
this.state.selected = null;
|
||||||
|
this.hideSelectedItem();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalClose = e.target.closest('.modal-close');
|
||||||
|
if (modalClose) {
|
||||||
|
this.state.selected = null;
|
||||||
|
this.hideSelectedItem();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextItem = e.target.closest('.modal-next');
|
||||||
|
if (nextItem && CncfLandscapeApp.nextItemId) {
|
||||||
|
CncfLandscapeApp.state.selected = CncfLandscapeApp.nextItemId;
|
||||||
|
this.showSelectedItem(CncfLandscapeApp.state.selected);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevItem = e.target.closest('.modal-prev');
|
||||||
|
if (prevItem && CncfLandscapeApp.prevItemId) {
|
||||||
|
CncfLandscapeApp.state.selected = CncfLandscapeApp.prevItemId;
|
||||||
|
this.showSelectedItem(CncfLandscapeApp.state.selected);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedItemInternalLinkEl = e.target.closest('.modal-body a[data-type=internal]')
|
||||||
|
if (selectedItemInternalLinkEl) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// support custom css styles and custom js eval code through iframe
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
var data = event.data;
|
||||||
|
if (data.type === "css") {
|
||||||
|
var styles = data.css;
|
||||||
|
var el = document.createElement('style');
|
||||||
|
el.type = 'text/css';
|
||||||
|
if (el.styleSheet) {
|
||||||
|
el.styleSheet.cssText = styles;
|
||||||
|
} else {
|
||||||
|
el.appendChild(document.createTextNode(styles));
|
||||||
|
}
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(el);
|
||||||
|
}
|
||||||
|
if (data.type === "js") {
|
||||||
|
eval(data.js);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// support css styles via param
|
||||||
|
const params = new URLSearchParams(window.location.search.substring(1));
|
||||||
|
if (params.get('css')) {
|
||||||
|
const element = document.createElement("link");
|
||||||
|
element.setAttribute("rel", "stylesheet");
|
||||||
|
element.setAttribute("type", "text/css");
|
||||||
|
element.setAttribute("href", params.get('css'));
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(element);
|
||||||
|
}
|
||||||
|
if (params.get('style')) {
|
||||||
|
const element = document.createElement("style");
|
||||||
|
let style = params.get('style');
|
||||||
|
try {
|
||||||
|
style = JSON.parse(style)
|
||||||
|
} catch(ex) {
|
||||||
|
|
||||||
|
}
|
||||||
|
element.innerHTML = style;
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(element);
|
||||||
|
}
|
||||||
|
this.showSelectedItem(this.state.selected);
|
||||||
|
},
|
||||||
|
|
||||||
|
showSelectedItem: async function(selectedItemId) {
|
||||||
|
const allIdElements = document.querySelectorAll(`[data-id]`);
|
||||||
|
let allIds = []
|
||||||
|
for (let element of allIdElements) {
|
||||||
|
allIds.push(element.getAttribute('data-id'));
|
||||||
|
}
|
||||||
|
this.selectedItems = this.selectedItems || {};
|
||||||
|
if (!this.selectedItems[selectedItemId]) {
|
||||||
|
const result = await fetch(`${this.basePath}/data/items/info-${selectedItemId}.html`);
|
||||||
|
const text = await result.text();
|
||||||
|
this.selectedItems[selectedItemId] = text;
|
||||||
|
}
|
||||||
|
document.querySelector('.modal').style.display="";
|
||||||
|
document.querySelector('.modal .modal-content').outerHTML = this.selectedItems[selectedItemId];
|
||||||
|
document.querySelector('body').style.overflow = 'hidden';
|
||||||
|
|
||||||
|
if (window.twttr) {
|
||||||
|
window.twttr.widgets.load();
|
||||||
|
} else {
|
||||||
|
setTimeout( () => window.twttr && window.twttr.widgets.load(), 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
//calculate previous and next items;
|
||||||
|
const index = allIds.indexOf(selectedItemId);
|
||||||
|
const prevItem = allIds[index - 1] || null;
|
||||||
|
const nextItem = allIds[index + 1] || null;
|
||||||
|
|
||||||
|
this.nextItemId = nextItem;
|
||||||
|
this.prevItemId = prevItem;
|
||||||
|
|
||||||
|
if (nextItem) {
|
||||||
|
document.querySelector('.modal-next').removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.modal-next').setAttribute('disabled', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevItem) {
|
||||||
|
document.querySelector('.modal-prev').removeAttribute('disabled');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.modal-prev').setAttribute('disabled', '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hideSelectedItem: function() {
|
||||||
|
window.parent.postMessage({type: 'landscapeapp-hide'}, '*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', () => CncfLandscapeApp.init());
|
121
src/script.js
|
@ -14,10 +14,18 @@ const CncfLandscapeApp = {
|
||||||
|
|
||||||
if (CncfLandscapeApp.state.embed) {
|
if (CncfLandscapeApp.state.embed) {
|
||||||
document.querySelector('html').classList.add('embed');
|
document.querySelector('html').classList.add('embed');
|
||||||
setInterval(function() {
|
if (CncfLandscapeApp.state.showModal) {
|
||||||
document.body.style.height = document.querySelector('.column-content').scrollHeight;
|
document.querySelector('html').classList.add('modal-embed')
|
||||||
}, 1000);
|
} else {
|
||||||
document.querySelector('#embedded-footer a').href = this.stringifyBrowserUrl({...this.state, embed: false});
|
setInterval(function() {
|
||||||
|
const realHeight = document.querySelector('.column-content').scrollHeight + 80;
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'landscapeapp-resize',
|
||||||
|
height: realHeight
|
||||||
|
}, '*');
|
||||||
|
}, 1000);
|
||||||
|
document.querySelector('#embedded-footer a').href = this.stringifyBrowserUrl({...this.state, embed: false});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (CncfLandscapeApp.state.cardStyle === 'borderless') {
|
if (CncfLandscapeApp.state.cardStyle === 'borderless') {
|
||||||
document.querySelector('html').classList.add('borderless-mode');
|
document.querySelector('html').classList.add('borderless-mode');
|
||||||
|
@ -42,6 +50,16 @@ const CncfLandscapeApp = {
|
||||||
} else if (document.querySelector('.select-popup').style.display === '') {
|
} else if (document.querySelector('.select-popup').style.display === '') {
|
||||||
document.querySelector('.select-popup').style.display = "none";
|
document.querySelector('.select-popup').style.display = "none";
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (document.querySelector('.select-popup').style.display === '') {
|
||||||
|
if (e.key >= 'a' && e.key <= 'z') {
|
||||||
|
const items = [...document.querySelectorAll('.select-popup .pure-material-checkbox span')];
|
||||||
|
const first = items.find( (i) => i.innerText.toLowerCase().startsWith(e.key.toLowerCase()));
|
||||||
|
if (first) {
|
||||||
|
first.scrollIntoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +177,7 @@ const CncfLandscapeApp = {
|
||||||
const linkState = this.parseUrl({search: groupingInternalLinkEl.getAttribute('href'), pathname: '', hash: ''});
|
const linkState = this.parseUrl({search: groupingInternalLinkEl.getAttribute('href'), pathname: '', hash: ''});
|
||||||
|
|
||||||
const f = (name, x) => this.calculateFullSelection(name, x);
|
const f = (name, x) => this.calculateFullSelection(name, x);
|
||||||
const allowedProps = ['grouping', 'sort', 'bestpractices', 'enduser', 'parent', 'language'];
|
const allowedProps = ['grouping', 'sort', 'bestpractices', 'enduser', 'parent', 'language', 'specification'];
|
||||||
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
||||||
for (let key of allowedProps) {
|
for (let key of allowedProps) {
|
||||||
newState[key] = linkState[key] || CncfLandscapeApp.initialState[key];
|
newState[key] = linkState[key] || CncfLandscapeApp.initialState[key];
|
||||||
|
@ -187,7 +205,7 @@ const CncfLandscapeApp = {
|
||||||
// Only set certain properties: filterable + invisible filters
|
// Only set certain properties: filterable + invisible filters
|
||||||
// for visible filter we need to always expand a current selection
|
// for visible filter we need to always expand a current selection
|
||||||
const f = (name, x) => this.calculateFullSelection(name, x);
|
const f = (name, x) => this.calculateFullSelection(name, x);
|
||||||
const allowedProps = ['grouping', 'sort', 'bestpractices', 'enduser', 'parent', 'language'];
|
const allowedProps = ['grouping', 'sort', 'bestpractices', 'enduser', 'parent', 'language', 'specification'];
|
||||||
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
||||||
for (let key of allowedProps) {
|
for (let key of allowedProps) {
|
||||||
newState[key] = linkState[key] || CncfLandscapeApp.initialState[key];
|
newState[key] = linkState[key] || CncfLandscapeApp.initialState[key];
|
||||||
|
@ -264,7 +282,7 @@ const CncfLandscapeApp = {
|
||||||
const newState = {...CncfLandscapeApp.state };
|
const newState = {...CncfLandscapeApp.state };
|
||||||
|
|
||||||
const f = (name, x) => this.calculateFullSelection(name, x);
|
const f = (name, x) => this.calculateFullSelection(name, x);
|
||||||
const allowedProps = ['bestpractices', 'enduser', 'parent', 'language'];
|
const allowedProps = ['bestpractices', 'enduser', 'parent', 'language', 'specification'];
|
||||||
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
const otherProps = ['category', 'project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
||||||
for (let key of allowedProps) {
|
for (let key of allowedProps) {
|
||||||
newState[key] = CncfLandscapeApp.initialState[key];
|
newState[key] = CncfLandscapeApp.initialState[key];
|
||||||
|
@ -528,7 +546,6 @@ const CncfLandscapeApp = {
|
||||||
}
|
}
|
||||||
let hasTrue = false;
|
let hasTrue = false;
|
||||||
let hasChildren = false;
|
let hasChildren = false;
|
||||||
let children = [];
|
|
||||||
for (let i = parentIndex + 1; i < allItems.length; i++) {
|
for (let i = parentIndex + 1; i < allItems.length; i++) {
|
||||||
const childEl = allItems[i];
|
const childEl = allItems[i];
|
||||||
if (childEl.getAttribute('data-level') === "1") {
|
if (childEl.getAttribute('data-level') === "1") {
|
||||||
|
@ -646,6 +663,7 @@ const CncfLandscapeApp = {
|
||||||
|
|
||||||
const parseMode = (x) => (x || '').indexOf('-mode') !== -1 ? 'card' : (x || CncfLandscapeApp.initialMode);
|
const parseMode = (x) => (x || '').indexOf('-mode') !== -1 ? 'card' : (x || CncfLandscapeApp.initialMode);
|
||||||
const parseCardStyle = (x) => (x || '').indexOf('-mode') !== -1 ? x.replace('-mode', '') : 'card';
|
const parseCardStyle = (x) => (x || '').indexOf('-mode') !== -1 ? x.replace('-mode', '') : 'card';
|
||||||
|
const parseParamStyle = (x) => ['logo', 'borderless', 'flat'].indexOf(x) !== -1 ? x : '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
zoom: +params.get('zoom') / 100 || 1,
|
zoom: +params.get('zoom') / 100 || 1,
|
||||||
|
@ -654,7 +672,7 @@ const CncfLandscapeApp = {
|
||||||
activeSection: hash,
|
activeSection: hash,
|
||||||
|
|
||||||
mode: parseMode(params.get('format') || pathname) || CncfLandscapeApp.initialMode,
|
mode: parseMode(params.get('format') || pathname) || CncfLandscapeApp.initialMode,
|
||||||
cardStyle: params.get('style') || parseCardStyle(pathname),
|
cardStyle: parseParamStyle(params.get('style')) || parseCardStyle(pathname),
|
||||||
|
|
||||||
grouping: params.get('grouping') || 'project',
|
grouping: params.get('grouping') || 'project',
|
||||||
sort: params.get('sort') || 'name',
|
sort: params.get('sort') || 'name',
|
||||||
|
@ -671,9 +689,10 @@ const CncfLandscapeApp = {
|
||||||
enduser: params.get('enduser') || '',
|
enduser: params.get('enduser') || '',
|
||||||
parent: params.get('parent') || '',
|
parent: params.get('parent') || '',
|
||||||
language: params.get('language') || '',
|
language: params.get('language') || '',
|
||||||
|
specification: params.get('specification') || '',
|
||||||
selected: params.get('selected') || null,
|
selected: params.get('selected') || null,
|
||||||
embed: params.has('embed'),
|
embed: params.has('embed'),
|
||||||
|
showModal: params.has('showmodal'),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
propagateStateToUiAndUrl: function() {
|
propagateStateToUiAndUrl: function() {
|
||||||
|
@ -701,6 +720,18 @@ const CncfLandscapeApp = {
|
||||||
assignSingleSelect('sort');
|
assignSingleSelect('sort');
|
||||||
assignSingleSelect('grouping');
|
assignSingleSelect('grouping');
|
||||||
assignMultiSelect('category');
|
assignMultiSelect('category');
|
||||||
|
|
||||||
|
const isCardMode = CncfLandscapeApp.state.mode === 'card';
|
||||||
|
if (isCardMode) {
|
||||||
|
document.querySelector('.select[data-name=sort]').classList.remove('select-disabled');
|
||||||
|
document.querySelector('.select[data-name=grouping]').classList.remove('select-disabled');
|
||||||
|
document.querySelector('.select[data-name=category]').classList.remove('select-disabled');
|
||||||
|
} else {
|
||||||
|
document.querySelector('.select[data-name=sort]').classList.add('select-disabled');
|
||||||
|
document.querySelector('.select[data-name=grouping]').classList.add('select-disabled');
|
||||||
|
document.querySelector('.select[data-name=category]').classList.add('select-disabled');
|
||||||
|
}
|
||||||
|
|
||||||
assignMultiSelect('project');
|
assignMultiSelect('project');
|
||||||
assignMultiSelect('license');
|
assignMultiSelect('license');
|
||||||
assignMultiSelect('organization');
|
assignMultiSelect('organization');
|
||||||
|
@ -735,7 +766,7 @@ const CncfLandscapeApp = {
|
||||||
if (item.level === 1) {
|
if (item.level === 1) {
|
||||||
let allChildren = [];
|
let allChildren = [];
|
||||||
let selectedChildren = [];
|
let selectedChildren = [];
|
||||||
for (j = i + 1; j < wrapper.selectData.length; j++) {
|
for (let j = i + 1; j < wrapper.selectData.length; j++) {
|
||||||
let childItem = wrapper.selectData[j];
|
let childItem = wrapper.selectData[j];
|
||||||
if (childItem.level === 1) {
|
if (childItem.level === 1) {
|
||||||
break;
|
break;
|
||||||
|
@ -776,7 +807,7 @@ const CncfLandscapeApp = {
|
||||||
if (item.level === 1 && selectedIds.includes(item.id)) {
|
if (item.level === 1 && selectedIds.includes(item.id)) {
|
||||||
let children = [];
|
let children = [];
|
||||||
let totalChildren = 0;
|
let totalChildren = 0;
|
||||||
for (j = i + 1; j < wrapper.selectData.length; j++) {
|
for (let j = i + 1; j < wrapper.selectData.length; j++) {
|
||||||
let childItem = wrapper.selectData[j];
|
let childItem = wrapper.selectData[j];
|
||||||
if (childItem.level === 1) {
|
if (childItem.level === 1) {
|
||||||
break;
|
break;
|
||||||
|
@ -813,7 +844,7 @@ const CncfLandscapeApp = {
|
||||||
params[field] = CncfLandscapeApp.calculateShortSelection(field).url
|
params[field] = CncfLandscapeApp.calculateShortSelection(field).url
|
||||||
}
|
}
|
||||||
// no fields for certain filters yet
|
// no fields for certain filters yet
|
||||||
for (let field of ['sort', 'grouping', 'bestpractices', 'enduser', 'parent', 'language']) {
|
for (let field of ['sort', 'grouping', 'bestpractices', 'enduser', 'parent', 'language', 'specification']) {
|
||||||
params[field] = state[field]
|
params[field] = state[field]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -846,14 +877,13 @@ const CncfLandscapeApp = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no fields for certain filters yet
|
// no fields for certain filters yet
|
||||||
for (let field of ['grouping', 'sort', 'selected', 'bestpractices', 'enduser', 'parent', 'language',
|
for (let field of ['grouping', 'sort', 'selected', 'bestpractices', 'enduser', 'parent', 'language', 'specification', 'fullscreen', 'embed']) {
|
||||||
'fullscreen', 'embed']) {
|
|
||||||
if (state[field] !== initialState[field]) {
|
if (state[field] !== initialState[field]) {
|
||||||
params[field] = state[field]
|
params[field] = state[field]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.zoom !== initialState.zoom) {
|
if (state.zoom !== initialState.zoom) {
|
||||||
params.zoom = (state.zoom * 100).toFixed(0);;
|
params.zoom = (state.zoom * 100).toFixed(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -870,6 +900,18 @@ const CncfLandscapeApp = {
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
showSelectedItem: async function(selectedItemId) {
|
showSelectedItem: async function(selectedItemId) {
|
||||||
|
if (this.state.embed && !this.state.showModal) {
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'landscapeapp-show',
|
||||||
|
selected: selectedItemId,
|
||||||
|
location: {
|
||||||
|
search: window.location.search,
|
||||||
|
pathname: window.location.pathname
|
||||||
|
}
|
||||||
|
}, '*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.selectedItems = this.selectedItems || {};
|
this.selectedItems = this.selectedItems || {};
|
||||||
if (!this.selectedItems[selectedItemId]) {
|
if (!this.selectedItems[selectedItemId]) {
|
||||||
const result = await fetch(`${this.basePath}/data/items/info-${selectedItemId}.html`);
|
const result = await fetch(`${this.basePath}/data/items/info-${selectedItemId}.html`);
|
||||||
|
@ -890,7 +932,7 @@ const CncfLandscapeApp = {
|
||||||
let prevItem = null;
|
let prevItem = null;
|
||||||
let nextItem = null;
|
let nextItem = null;
|
||||||
if (this.state.mode === 'card') {
|
if (this.state.mode === 'card') {
|
||||||
const items = this.groupedItems.flatMap( (x) => x.items);
|
const items = (this.groupedItems || []).flatMap( (x) => x.items);
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
if (items[i].id === selectedItemId) {
|
if (items[i].id === selectedItemId) {
|
||||||
prevItem = (items[i - 1] || {}).id;
|
prevItem = (items[i - 1] || {}).id;
|
||||||
|
@ -921,35 +963,11 @@ const CncfLandscapeApp = {
|
||||||
} else {
|
} else {
|
||||||
document.querySelector('.modal-prev').setAttribute('disabled', '');
|
document.querySelector('.modal-prev').setAttribute('disabled', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.parentIFrame) {
|
|
||||||
window.parentIFrame.sendMessage({type: 'showModal'});
|
|
||||||
window.parentIFrame.getPageInfo(function(info) {
|
|
||||||
var offset = info.scrollTop - info.offsetTop;
|
|
||||||
var maxHeight = info.clientHeight * 0.9;
|
|
||||||
if (maxHeight > 640) {
|
|
||||||
maxHeight = 640;
|
|
||||||
}
|
|
||||||
var defaultTop = (info.windowHeight - maxHeight) / 2;
|
|
||||||
var top = defaultTop + offset;
|
|
||||||
if (top < 0 && info.iframeHeight <= 600) {
|
|
||||||
top = 10;
|
|
||||||
}
|
|
||||||
setTimeout(function() {
|
|
||||||
const modal = document.querySelector('.modal-body');
|
|
||||||
if (modal) {
|
|
||||||
modal.style.top = top + 'px';
|
|
||||||
modal.style.marginTop = 0;
|
|
||||||
modal.style.marginBottom = 0;
|
|
||||||
modal.style.bottom = '';
|
|
||||||
modal.style.maxHeight = maxHeight + 'px';
|
|
||||||
}
|
|
||||||
}, 10);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
updateUrl: function() {
|
updateUrl: function() {
|
||||||
|
if (CncfLandscapeApp.state.embed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newUrl = CncfLandscapeApp.stringifyBrowserUrl(CncfLandscapeApp.state);
|
const newUrl = CncfLandscapeApp.stringifyBrowserUrl(CncfLandscapeApp.state);
|
||||||
if (newUrl !== this.previousUrl) {
|
if (newUrl !== this.previousUrl) {
|
||||||
history.pushState(CncfLandscapeApp.state, '', newUrl);
|
history.pushState(CncfLandscapeApp.state, '', newUrl);
|
||||||
|
@ -958,12 +976,12 @@ const CncfLandscapeApp = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hideSelectedItem: function() {
|
hideSelectedItem: function() {
|
||||||
|
if (this.state.showModal) {
|
||||||
|
window.parent.postMessage({type: 'landscapeapp-hide'}, '*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
document.querySelector('.modal').style.display="none";
|
document.querySelector('.modal').style.display="none";
|
||||||
document.querySelector('body').style.overflow = '';
|
document.querySelector('body').style.overflow = '';
|
||||||
if (window.parentIFrame && this.state.embed) {
|
|
||||||
window.parentIFrame.sendMessage({type: 'hideModal'})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateUrl();
|
this.updateUrl();
|
||||||
},
|
},
|
||||||
fetchApiData: async function() {
|
fetchApiData: async function() {
|
||||||
|
@ -1045,7 +1063,7 @@ const CncfLandscapeApp = {
|
||||||
|
|
||||||
// edge case: we just opened a tab without filters - then just display everything!
|
// edge case: we just opened a tab without filters - then just display everything!
|
||||||
if (this.state.mode === this.initialMode) {
|
if (this.state.mode === this.initialMode) {
|
||||||
const allowedProps = ['bestpractices', 'enduser', 'parent', 'language'];
|
const allowedProps = ['bestpractices', 'enduser', 'parent', 'language', 'specification'];
|
||||||
const otherProps = ['project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
const otherProps = ['project', 'license', 'organization', 'headquarters', 'company-type', 'industries']
|
||||||
let same = true;
|
let same = true;
|
||||||
for (let key of [...allowedProps, ...otherProps]) {
|
for (let key of [...allowedProps, ...otherProps]) {
|
||||||
|
@ -1115,6 +1133,7 @@ const CncfLandscapeApp = {
|
||||||
result[card.getAttribute('data-id')] = card;
|
result[card.getAttribute('data-id')] = card;
|
||||||
}
|
}
|
||||||
this.cards = result;
|
this.cards = result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiData = await this.fetchApiData();
|
const apiData = await this.fetchApiData();
|
||||||
|
@ -1154,6 +1173,10 @@ const CncfLandscapeApp = {
|
||||||
}
|
}
|
||||||
document.querySelector('.column-content').replaceChildren(fragment);
|
document.querySelector('.column-content').replaceChildren(fragment);
|
||||||
this.lastCards = JSON.stringify(this.groupedItems);
|
this.lastCards = JSON.stringify(this.groupedItems);
|
||||||
|
|
||||||
|
if (this.state.selected) {
|
||||||
|
this.propagateStateToUi();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener('DOMContentLoaded', () => CncfLandscapeApp.init());
|
document.addEventListener('DOMContentLoaded', () => CncfLandscapeApp.init());
|
||||||
|
|
|
@ -249,6 +249,42 @@ a:hover svg {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#guide-page .links {
|
||||||
|
position: fixed;
|
||||||
|
right: 10px;
|
||||||
|
top: 100px;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 130px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1250px) {
|
||||||
|
#guide-page .links {
|
||||||
|
position: inherit;
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
#guide-page .links > div {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#guide-page .links > div {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#guide-page .links svg {
|
||||||
|
fill: currentColor;
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#guide-page h1 {
|
#guide-page h1 {
|
||||||
font-size: 2.5em;
|
font-size: 2.5em;
|
||||||
}
|
}
|
||||||
|
@ -646,10 +682,11 @@ a:hover svg {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin: 15px 0px;
|
margin: 15px 0px;
|
||||||
max-width: 165px;
|
max-width: 165px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.guide-toggle .toggle-item {
|
.guide-toggle .toggle-item {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
width: 50%;
|
width: 49%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: rgb(46, 103, 191);
|
color: rgb(46, 103, 191);
|
||||||
|
@ -1292,6 +1329,7 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 40px;
|
min-width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-top: -6px;
|
||||||
}
|
}
|
||||||
.right-buttons .disabled {
|
.right-buttons .disabled {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
@ -1305,6 +1343,16 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.embed.modal-embed .column-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.embed.modal-embed {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
.embed.modal-embed #embedded-footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tweet-button {
|
.tweet-button {
|
||||||
width: 105px;
|
width: 105px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1415,6 +1463,13 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
height: 63px;
|
height: 63px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inner-landscape .large-item.large-item-16, .items .large-item.large-item-16 {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
width: 145px;
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
.inner-landscape .small-item, .items .small-item {
|
.inner-landscape .small-item, .items .small-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1433,11 +1488,16 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
grid-row-end: span 1;
|
grid-row-end: span 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-landscape .item-wrapper.wrapper-large, .items .item-wrapper.wrapper-large {
|
.inner-landscape .item-wrapper.wrapper-large-4, .items .item-wrapper.wrapper-large-4 {
|
||||||
grid-column-end: span 2;
|
grid-column-end: span 2;
|
||||||
grid-row-end: span 2;
|
grid-row-end: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inner-landscape .item-wrapper.wrapper-large-16, .items .item-wrapper.wrapper-large-16 {
|
||||||
|
grid-column-end: span 4;
|
||||||
|
grid-row-end: span 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: var(--sm-screen)) {
|
@media (max-width: var(--sm-screen)) {
|
||||||
|
@ -1635,6 +1695,7 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
height: 180px;
|
height: 180px;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content .product-tags {
|
.modal-content .product-tags {
|
||||||
|
@ -1830,6 +1891,31 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
height: 22px;
|
height: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.twolines {
|
||||||
|
height: 22px !important;
|
||||||
|
width: 145px !important;
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.twolines .tag-name {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
top: -1px;
|
||||||
|
width: 145px;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
.twolines .tag-value {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
width: 145px;
|
||||||
|
white-space: normal !important;
|
||||||
|
top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.modal-content .single-column .product-property .col:nth-child(1) {
|
.modal-content .single-column .product-property .col:nth-child(1) {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
@ -1847,6 +1933,10 @@ html.ios.has-selected-item, html.ios.has-selected-item body {
|
||||||
width: 180px;
|
width: 180px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.select-disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.select-text {
|
.select-text {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 300 244">
|
||||||
|
<g transform="translate(-539.17946,-568.85777)" >
|
||||||
|
<path fillOpacity="1" fillRule="nonzero"
|
||||||
|
d="m 633.89823,812.04479 c 112.46038,0 173.95627,-93.16765 173.95627,-173.95625 0,-2.64628 -0.0539,-5.28062 -0.1726,-7.90305 11.93799,-8.63016 22.31446,-19.39999 30.49762,-31.65984 -10.95459,4.86937 -22.74358,8.14741 -35.11071,9.62551 12.62341,-7.56929 22.31446,-19.54304 26.88583,-33.81739 -11.81284,7.00307 -24.89517,12.09297 -38.82383,14.84055 -11.15723,-11.88436 -27.04079,-19.31655 -44.62892,-19.31655 -33.76374,0 -61.14426,27.38052 -61.14426,61.13233 0,4.79784 0.5364,9.46458 1.58538,13.94057 -50.81546,-2.55686 -95.87353,-26.88582 -126.02546,-63.87991 -5.25082,9.03545 -8.27852,19.53111 -8.27852,30.73006 0,21.21186 10.79366,39.93837 27.20766,50.89296 -10.03077,-0.30992 -19.45363,-3.06348 -27.69044,-7.64676 -0.009,0.25652 -0.009,0.50661 -0.009,0.78077 0,29.60957 21.07478,54.3319 49.0513,59.93435 -5.13757,1.40062 -10.54335,2.15158 -16.12196,2.15158 -3.93364,0 -7.76596,-0.38716 -11.49099,-1.1026 7.78383,24.2932 30.35457,41.97073 57.11525,42.46543 -20.92578,16.40207 -47.28712,26.17062 -75.93712,26.17062 -4.92898,0 -9.79834,-0.28036 -14.58427,-0.84634 27.05868,17.34379 59.18936,27.46396 93.72193,27.46396" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M11.013 1.427a1.75 1.75 0 012.474 0l1.086 1.086a1.75 1.75 0 010 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 01-.927-.928l.929-3.25a1.75 1.75 0 01.445-.758l8.61-8.61zm1.414 1.06a.25.25 0 00-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 000-.354l-1.086-1.086zM11.189 6.25L9.75 4.81l-6.286 6.287a.25.25 0 00-.064.108l-.558 1.953 1.953-.558a.249.249 0 00.108-.064l6.286-6.286z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 470 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M10 17l5-5-5-5v10z"></path></svg>
|
After Width: | Height: | Size: 68 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14zm-1-6h-3V8h-2v5H8l4 4 4-4z"></path></svg>
|
After Width: | Height: | Size: 180 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></svg>
|
After Width: | Height: | Size: 127 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></svg>
|
After Width: | Height: | Size: 127 B |
|
@ -1,4 +1,4 @@
|
||||||
export const iconGithub = <svg viewBox="0 0 24 24">
|
<svg viewBox="0 0 24 24">
|
||||||
<path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58
|
<path d="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58
|
||||||
9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81
|
9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81
|
||||||
5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18
|
5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18
|
||||||
|
@ -7,12 +7,4 @@ export const iconGithub = <svg viewBox="0 0 24 24">
|
||||||
13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39
|
13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39
|
||||||
18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68
|
18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68
|
||||||
14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
|
14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" />
|
||||||
</svg>;
|
</svg>
|
||||||
|
|
||||||
export const iconStar = <svg viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></svg>
|
|
||||||
|
|
||||||
export const guideIcon =
|
|
||||||
<svg className="guide-icon" viewBox="0 0 18.78 23.66">
|
|
||||||
<path d="M9.39,9.5a2.09,2.09,0,0,1,0-4.18,2.09,2.09,0,0,1,0,4.18Z"/>
|
|
||||||
<path d="M3,19.7H17.75a1,1,0,0,0,1-1V1a1,1,0,0,0-1-1H1A1,1,0,0,0,0,1V20.64a3,3,0,0,0,3,3H17.75a1,1,0,1,0,0-2.05H3A1,1,0,0,1,3,19.7ZM9.39,2.9a4.52,4.52,0,0,1,4.5,4.51c0,1.76-2.29,6-3.61,8.27a1,1,0,0,1-1.79,0C7.18,13.41,4.88,9.17,4.88,7.41A4.52,4.52,0,0,1,9.39,2.9Z"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 826 B |
|
@ -0,0 +1,4 @@
|
||||||
|
<svg class="guide-icon" viewBox="0 0 18.78 23.66">
|
||||||
|
<path d="M9.39,9.5a2.09,2.09,0,0,1,0-4.18,2.09,2.09,0,0,1,0,4.18Z"/>
|
||||||
|
<path d="M3,19.7H17.75a1,1,0,0,0,1-1V1a1,1,0,0,0-1-1H1A1,1,0,0,0,0,1V20.64a3,3,0,0,0,3,3H17.75a1,1,0,1,0,0-2.05H3A1,1,0,0,1,3,19.7ZM9.39,2.9a4.52,4.52,0,0,1,4.5,4.51c0,1.76-2.29,6-3.61,8.27a1,1,0,0,1-1.79,0C7.18,13.41,4.88,9.17,4.88,7.41A4.52,4.52,0,0,1,9.39,2.9Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 402 B |
|
@ -0,0 +1,3 @@
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||||
|
<path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 223 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"></path></svg>
|
After Width: | Height: | Size: 115 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></path></svg>
|
After Width: | Height: | Size: 114 B |
|
@ -0,0 +1,2 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M14 12c0-1.1-.9-2-2-2s-2 .9-2 2 .9 2 2 2 2-.9 2-2zm-2-9c-4.97 0-9 4.03-9 9H0l4 4 4-4H5c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.51 0-2.91-.49-4.06-1.3l-1.42 1.44C8.04 20.3 9.94 21 12 21c4.97 0 9-4.03 9-9s-4.03-9-9-9z"/></svg>
|
||||||
|
|
After Width: | Height: | Size: 263 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
|
After Width: | Height: | Size: 95 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"></path></svg>
|
After Width: | Height: | Size: 138 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path></svg>
|
After Width: | Height: | Size: 150 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11H7v-2h10v2z"></path></svg>
|
After Width: | Height: | Size: 132 B |
|
@ -9,13 +9,13 @@
|
||||||
// url(id by default): how the value is stored in the url
|
// url(id by default): how the value is stored in the url
|
||||||
// sortOrder(element index by default): sort order when grouping
|
// sortOrder(element index by default): sort order when grouping
|
||||||
// match: function
|
// match: function
|
||||||
import path from 'path';
|
const _ = require('lodash');
|
||||||
import fs from 'fs';
|
|
||||||
import _ from 'lodash';
|
const { isParent } = require('../utils/isParent');
|
||||||
import lookups from 'project/lookup'
|
const { readJsonFromProject, readJsonFromDist } = require('../utils/readJson');
|
||||||
import unpack from '../utils/unpackArray';
|
|
||||||
import settings from 'dist/settings'
|
const lookups = readJsonFromProject('lookup');
|
||||||
import isParent from '../utils/isParent';
|
const settings = readJsonFromDist('settings');
|
||||||
|
|
||||||
const relationField = (function() {
|
const relationField = (function() {
|
||||||
const additionalRelations = settings.relation.values.flatMap(({ children }) => children || [])
|
const additionalRelations = settings.relation.values.flatMap(({ children }) => children || [])
|
||||||
|
@ -86,7 +86,7 @@ const fields = {
|
||||||
id: 'license',
|
id: 'license',
|
||||||
label: 'License',
|
label: 'License',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.license) || []),
|
values: [].concat(lookups.license || []),
|
||||||
processValuesBeforeSaving: function(values) {
|
processValuesBeforeSaving: function(values) {
|
||||||
return processValuesBeforeSaving({options: fields.license.values, values: values});
|
return processValuesBeforeSaving({options: fields.license.values, values: values});
|
||||||
},
|
},
|
||||||
|
@ -102,13 +102,13 @@ const fields = {
|
||||||
id: 'organization',
|
id: 'organization',
|
||||||
label: 'Organization',
|
label: 'Organization',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.organization) || [])
|
values: [].concat(lookups.organization || [])
|
||||||
},
|
},
|
||||||
headquarters: {
|
headquarters: {
|
||||||
id: 'headquarters',
|
id: 'headquarters',
|
||||||
label: 'Headquarters Location',
|
label: 'Headquarters Location',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.headquarters) || []),
|
values: [].concat(lookups.headquarters || []),
|
||||||
processValuesBeforeSaving: function(values) {
|
processValuesBeforeSaving: function(values) {
|
||||||
return processValuesBeforeSaving({options: fields.headquarters.values, values: values});
|
return processValuesBeforeSaving({options: fields.headquarters.values, values: values});
|
||||||
},
|
},
|
||||||
|
@ -121,7 +121,7 @@ const fields = {
|
||||||
url: 'company-type',
|
url: 'company-type',
|
||||||
label: 'Company Type',
|
label: 'Company Type',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.companyTypes) || []),
|
values: [].concat(lookups.companyTypes || []),
|
||||||
filterFn: function(filter, _, record) {
|
filterFn: function(filter, _, record) {
|
||||||
if (!filter || filter.length === 0) {
|
if (!filter || filter.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -135,7 +135,7 @@ const fields = {
|
||||||
id: 'industries',
|
id: 'industries',
|
||||||
label: 'Industries',
|
label: 'Industries',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.industries) || []),
|
values: [].concat(lookups.industries || []),
|
||||||
filterFn: function(filter, _, record) {
|
filterFn: function(filter, _, record) {
|
||||||
if (!filter || filter.length === 0) {
|
if (!filter || filter.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -150,7 +150,7 @@ const fields = {
|
||||||
url: 'category',
|
url: 'category',
|
||||||
label: 'Category',
|
label: 'Category',
|
||||||
isArray: true,
|
isArray: true,
|
||||||
values: [].concat(unpack(lookups.landscape) || []),
|
values: [].concat(lookups.landscape || []),
|
||||||
processValuesBeforeSaving: function(values) {
|
processValuesBeforeSaving: function(values) {
|
||||||
return processValuesBeforeSaving({options: fields.landscape.values, values: values});
|
return processValuesBeforeSaving({options: fields.landscape.values, values: values});
|
||||||
},
|
},
|
||||||
|
@ -233,6 +233,19 @@ const fields = {
|
||||||
},
|
},
|
||||||
values: [{id: true, label: 'Yes', url: 'yes'}, {id: false, label: 'No', url: 'no'}]
|
values: [{id: true, label: 'Yes', url: 'yes'}, {id: false, label: 'No', url: 'no'}]
|
||||||
},
|
},
|
||||||
|
specification: {
|
||||||
|
id: 'extra',
|
||||||
|
label: 'Specification',
|
||||||
|
url: 'specification',
|
||||||
|
filterFn: function(filter, value, item) {
|
||||||
|
if (filter === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const isSpecification = item.extra && (item.extra.specification === true || item.extra.specification === 'yes' || item.extra.specification === 'true');
|
||||||
|
return filter === true ? isSpecification : !isSpecification;
|
||||||
|
},
|
||||||
|
values: [{id: true, label: 'Yes', url: 'yes'}, {id: false, label: 'No', url: 'no'}]
|
||||||
|
},
|
||||||
parents: {
|
parents: {
|
||||||
id: 'parent',
|
id: 'parent',
|
||||||
url: 'parent',
|
url: 'parent',
|
||||||
|
@ -301,7 +314,7 @@ _.each(fields, function(field, key) {
|
||||||
|
|
||||||
field.valuesMap = _.keyBy(field.values, 'id')
|
field.valuesMap = _.keyBy(field.values, 'id')
|
||||||
});
|
});
|
||||||
export default fields;
|
module.exports.fields = fields;
|
||||||
|
|
||||||
const processValuesBeforeLoading = function({options, values}) {
|
const processValuesBeforeLoading = function({options, values}) {
|
||||||
return options.filter(function(option) {
|
return options.filter(function(option) {
|
||||||
|
@ -337,7 +350,7 @@ const processValuesBeforeSaving = function({options, values}) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// passed to the client
|
// passed to the client
|
||||||
export function options(field) {
|
function options(field) {
|
||||||
return fields[field].values.map(function(values) {
|
return fields[field].values.map(function(values) {
|
||||||
return {
|
return {
|
||||||
id: values.url,
|
id: values.url,
|
||||||
|
@ -346,8 +359,9 @@ export function options(field) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
module.exports.options = options;
|
||||||
|
|
||||||
export function filterFn({field, filters}) {
|
function filterFn({field, filters}) {
|
||||||
const fieldInfo = fields[field];
|
const fieldInfo = fields[field];
|
||||||
const filter = filters[field];
|
const filter = filters[field];
|
||||||
return function(x) {
|
return function(x) {
|
||||||
|
@ -369,12 +383,15 @@ export function filterFn({field, filters}) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function getGroupingValue({item, grouping, filters}) {
|
module.exports.filterFn = filterFn;
|
||||||
|
|
||||||
|
function getGroupingValue({item, grouping, filters}) {
|
||||||
const { id, groupFn } = fields[grouping];
|
const { id, groupFn } = fields[grouping];
|
||||||
return groupFn ? groupFn({ item , filters }) : item[id];
|
return groupFn ? groupFn({ item , filters }) : item[id];
|
||||||
}
|
}
|
||||||
|
module.exports.getGroupingValue = getGroupingValue;
|
||||||
|
|
||||||
export const sortOptions = [{
|
const sortOptions = [{
|
||||||
id: 'name',
|
id: 'name',
|
||||||
direction: 'asc',
|
direction: 'asc',
|
||||||
label: 'Alphabetical (a to z)',
|
label: 'Alphabetical (a to z)',
|
||||||
|
@ -413,4 +430,5 @@ export const sortOptions = [{
|
||||||
label: 'Date Joined',
|
label: 'Date Joined',
|
||||||
disabled: true
|
disabled: true
|
||||||
}]
|
}]
|
||||||
|
module.exports.sortOptions = sortOptions;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import getBasePath from '../../tools/getBasePath'
|
const { getBasePath } = require('../../tools/getBasePath');
|
||||||
|
|
||||||
const assetPath = path => {
|
module.exports.assetPath = path => {
|
||||||
if (path.startsWith('http://') || path.startsWith('https://')) {
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
return `${getBasePath()}${path[0] === '/' ? '' : '/'}${path}`
|
return `${getBasePath()}${path[0] === '/' ? '' : '/'}${path}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export default assetPath
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export function millify(value) {
|
module.exports.millify = function(value) {
|
||||||
let base, suffix;
|
let base, suffix;
|
||||||
if (value < 1000 - 0.05) {
|
if (value < 1000 - 0.05) {
|
||||||
base = value;
|
base = value;
|
||||||
|
@ -20,3 +20,21 @@ export function millify(value) {
|
||||||
return digits + suffix;
|
return digits + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.h = function(html) {
|
||||||
|
if (!html) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
var entityMap = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
'/': '/',
|
||||||
|
'`': '`',
|
||||||
|
'=': '='
|
||||||
|
};
|
||||||
|
return String(html).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
|
||||||
|
return entityMap[s];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import formatNumber from 'format-number';
|
const formatNumber = require('format-number');
|
||||||
export default function formatAmount(v) {
|
module.exports.formatAmount = function(v) {
|
||||||
if (_.isString(v)) {
|
if (_.isString(v)) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function formatCity({city, region, country}) {
|
module.exports.formatCity = function({city, region, country}) {
|
||||||
if (!city) {
|
if (!city) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import formatNumber from 'format-number';
|
const _formatNumber = require('format-number');
|
||||||
export default function _formatNumber(v) {
|
module.exports.formatNumber = function(v) {
|
||||||
if (_.isString(v)) {
|
if (_.isString(v)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return formatNumber({integerSeparator: ','})(v);
|
return _formatNumber({integerSeparator: ','})(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
import { getGroupedItemsForContentMode, flattenItems } from './itemsCalculator'
|
|
||||||
import { projects } from '../../tools/loadData'
|
|
||||||
|
|
||||||
const getPrerenderProps = params => {
|
|
||||||
const items = flattenItems(getGroupedItemsForContentMode(params, projects))
|
|
||||||
const entries = items.map(project => {
|
|
||||||
const keys = [
|
|
||||||
'name', 'stars', 'organization', 'path', 'landscape', 'category', 'oss', 'href', 'id',
|
|
||||||
'flatName', 'member', 'relation', 'project', 'isSubsidiaryProject', 'amount', 'amountKind',
|
|
||||||
'headquarters', 'license', 'bestPracticePercentage', 'enduser', 'joined', 'industries'
|
|
||||||
]
|
|
||||||
|
|
||||||
const entry = keys.reduce((hash, key) => {
|
|
||||||
const value = project[key]
|
|
||||||
return {
|
|
||||||
...hash,
|
|
||||||
...(value || value === false ? { [key]: project[key] } : {})
|
|
||||||
}
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
const languages = ((project.github_data || {}).languages || []).map(({ name }) => ({ name }))
|
|
||||||
const crunchbaseData = project.crunchbaseData || {}
|
|
||||||
|
|
||||||
const parents = crunchbaseData.parents || []
|
|
||||||
const company_type = crunchbaseData.company_type || ''
|
|
||||||
|
|
||||||
return {
|
|
||||||
...entry,
|
|
||||||
github_data: { languages },
|
|
||||||
crunchbaseData: { parents, company_type }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return { entries }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default getPrerenderProps
|
|
|
@ -1,8 +1,7 @@
|
||||||
import fields from '../types/fields';
|
const { fields } = require('../types/fields');
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
module.exports.groupingLabel = function(field, id) {
|
||||||
export default function groupingLabel(field, id) {
|
|
||||||
const values = fields[field].answers;
|
const values = fields[field].answers;
|
||||||
const valueInfo = _.find(values, {id: id});
|
const valueInfo = _.find(values, {id: id});
|
||||||
return valueInfo.groupingLabel;
|
return valueInfo.groupingLabel;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import fields from '../types/fields';
|
const { fields } = require('../types/fields');
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
export default function groupFn(field) {
|
module.exports.groupingOrder = function(field) {
|
||||||
const values = fields[field].answers;
|
const values = fields[field].answers;
|
||||||
const sortedValues = _.orderBy(values, 'groupingSortOrder');
|
const sortedValues = _.orderBy(values, 'groupingSortOrder');
|
||||||
return function(x) {
|
return function(x) {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
const files = require('fs').readdirSync('src/svg');
|
||||||
|
for (let file of files) {
|
||||||
|
if (file !== '.' && file !== '..' && file.endsWith('.svg')) {
|
||||||
|
const iconName = file.split('.svg')[0];
|
||||||
|
module.exports[iconName] = require('fs').readFileSync(`src/svg/${file}`, 'utf-8');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
const isParent = (urlOrSlug, project) => {
|
module.exports.isParent = (urlOrSlug, project) => {
|
||||||
if (urlOrSlug) {
|
if (urlOrSlug) {
|
||||||
const url = urlOrSlug.indexOf("crunchbase.com") > 0 ? urlOrSlug : `https://www.crunchbase.com/organization/${urlOrSlug}`;
|
const url = urlOrSlug.indexOf("crunchbase.com") > 0 ? urlOrSlug : `https://www.crunchbase.com/organization/${urlOrSlug}`;
|
||||||
|
|
||||||
return project.crunchbase === url || project.crunchbaseData.parents.includes(url);
|
return project.crunchbase === url || project.crunchbaseData.parents.includes(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default isParent
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import fields, { filterFn, getGroupingValue } from '../types/fields';
|
const { fields, filterFn, getGroupingValue } = require('../types/fields');
|
||||||
import groupingLabel from '../utils/groupingLabel';
|
const { groupingLabel } = require('../utils/groupingLabel');
|
||||||
import groupingOrder from '../utils/groupingOrder';
|
const { groupingOrder } = require('../utils/groupingOrder');
|
||||||
import formatAmount from '../utils/formatAmount';
|
const { stringOrSpecial } = require('../utils/stringOrSpecial');
|
||||||
import formatNumber from 'format-number';
|
const { getLandscapeCategories } = require('./sharedItemsCalculator');
|
||||||
import stringOrSpecial from '../utils/stringOrSpecial';
|
const { stringifyParams } = require('./routing');
|
||||||
import { getLandscapeCategories } from './sharedItemsCalculator';
|
|
||||||
import { findLandscapeSettings } from "./landscapeSettings";
|
|
||||||
import { stringifyParams } from './routing'
|
|
||||||
|
|
||||||
const landscape = fields.landscape.values;
|
const landscape = fields.landscape.values;
|
||||||
|
|
||||||
|
@ -15,18 +12,18 @@ const groupAndSort = (items, sortCriteria) => {
|
||||||
return _.groupBy(_.orderBy(items, sortCriteria), 'landscape')
|
return _.groupBy(_.orderBy(items, sortCriteria), 'landscape')
|
||||||
}
|
}
|
||||||
|
|
||||||
export const expandSecondPathItems = function(data) {
|
module.exports.expandSecondPathItems = function(data) {
|
||||||
const extraItems = data.filter( (x) => x.second_path).map( (item) => ({
|
const extraItems = data.filter( (x) => x.second_path).flatMap( (item) => [item.second_path].flat().map( (extraPath) => ({
|
||||||
...item,
|
...item,
|
||||||
category: item.second_path.split('/')[0].trim(),
|
category: extraPath.split('/')[0].trim(),
|
||||||
path: item.second_path,
|
path: extraPath,
|
||||||
landscape: item.second_path,
|
landscape: extraPath,
|
||||||
second_path: item.path
|
allPaths: [item.path.concat([item.second_path].flat())]
|
||||||
}));
|
})));
|
||||||
return data.concat(extraItems);
|
return data.concat(extraItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFilteredItems = function({data, filters}) {
|
const getFilteredItems = module.exports.getFilteredItems = function({data, filters}) {
|
||||||
var filterHostedProject = filterFn({field: 'relation', filters});
|
var filterHostedProject = filterFn({field: 'relation', filters});
|
||||||
var filterByLicense = filterFn({field: 'license', filters});
|
var filterByLicense = filterFn({field: 'license', filters});
|
||||||
var filterByOrganization = filterFn({field: 'organization', filters});
|
var filterByOrganization = filterFn({field: 'organization', filters});
|
||||||
|
@ -38,32 +35,14 @@ export const getFilteredItems = function({data, filters}) {
|
||||||
var filterByLanguage = filterFn({field: 'language', filters});
|
var filterByLanguage = filterFn({field: 'language', filters});
|
||||||
var filterByCompanyType = filterFn({field: 'companyType', filters});
|
var filterByCompanyType = filterFn({field: 'companyType', filters});
|
||||||
var filterByIndustries = filterFn({field: 'industries', filters});
|
var filterByIndustries = filterFn({field: 'industries', filters});
|
||||||
|
var filterBySpecification = filterFn({field: 'specification', filters});
|
||||||
return data.filter(function(x) {
|
return data.filter(function(x) {
|
||||||
return filterHostedProject(x) && filterByLicense(x) && filterByOrganization(x) && filterByHeadquarters(x) && filterByLandscape(x) && filterByBestPractices(x) && filterByEnduser(x) && filterByParent(x) && filterByLanguage(x) && filterByCompanyType(x) && filterByIndustries(x);
|
return filterHostedProject(x) && filterByLicense(x) && filterByOrganization(x) && filterByHeadquarters(x) && filterByLandscape(x) && filterByBestPractices(x) && filterByEnduser(x) && filterByParent(x) && filterByLanguage(x) && filterByCompanyType(x) && filterByIndustries(x) && filterBySpecification(x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const addExtraFields = function(data) {
|
|
||||||
return _.map(data, function(data) {
|
|
||||||
const hasStars = data.stars !== 'N/A' && data.stars !== 'Not Entered Yet';
|
|
||||||
const hasMarketCap = data.amount !== 'N/A' && data.amount !== 'Not Entered Yet';
|
|
||||||
return { ...data,
|
|
||||||
starsPresent: hasStars ,
|
|
||||||
starsAsText: hasStars ? formatNumber({integerSeparator: ','})(data.stars) : '',
|
|
||||||
marketCapPresent: hasMarketCap,
|
|
||||||
marketCapAsText: formatAmount(data.amount)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExtraFields = function({data, filters}) {
|
|
||||||
const filtered = getFilteredItems({data, filters});
|
|
||||||
return addExtraFields(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSortedItems = function({data, filters, sortField, sortDirection}) {
|
const getSortedItems = function({data, filters, sortField, sortDirection}) {
|
||||||
data = getExtraFields({data, filters });
|
data = getFilteredItems({data, filters});
|
||||||
|
|
||||||
const fieldInfo = fields[sortField];
|
const fieldInfo = fields[sortField];
|
||||||
const nonPublic = (x) => (x.name || '').toString().indexOf('Non-Public Organization') === 0;
|
const nonPublic = (x) => (x.name || '').toString().indexOf('Non-Public Organization') === 0;
|
||||||
const emptyItemsNA = data.filter(function(x) {
|
const emptyItemsNA = data.filter(function(x) {
|
||||||
|
@ -105,14 +84,16 @@ const getSortedItems = function({data, filters, sortField, sortDirection}) {
|
||||||
return sortedViaMainSort.concat(sortedViaName1).concat(sortedViaName2).concat(sortedViaName3).concat(sortedViaName4);
|
return sortedViaMainSort.concat(sortedViaName1).concat(sortedViaName2).concat(sortedViaName3).concat(sortedViaName4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGroupedItems = function({ data, filters, sortField, sortDirection, grouping }) {
|
const getGroupedItems = module.exports.getGroupedItems = function({ data, filters, sortField, sortDirection, grouping, skipDuplicates }) {
|
||||||
const items = getSortedItems({ data, filters, sortField, sortDirection });
|
const items = getSortedItems({ data, filters, sortField, sortDirection });
|
||||||
|
|
||||||
|
const uniq = skipDuplicates ? (x) => _.uniqBy(x, 'id') : (x) => x;
|
||||||
|
|
||||||
if (grouping === 'no') {
|
if (grouping === 'no') {
|
||||||
return [{
|
return [{
|
||||||
key: 'key',
|
key: 'key',
|
||||||
header: 'No Grouping',
|
header: 'No Grouping',
|
||||||
items: items
|
items: uniq(items)
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +108,7 @@ const getGroupedItems = function({ data, filters, sortField, sortDirection, grou
|
||||||
return {
|
return {
|
||||||
key: properKey,
|
key: properKey,
|
||||||
header: groupingLabel(grouping, properKey),
|
header: groupingLabel(grouping, properKey),
|
||||||
items: value,
|
items: uniq(value),
|
||||||
href: stringifyParams({filters: newFilters, grouping, sortField})
|
href: stringifyParams({filters: newFilters, grouping, sortField})
|
||||||
}
|
}
|
||||||
}), (group) => groupingOrder(grouping)(group.key));
|
}), (group) => groupingOrder(grouping)(group.key));
|
||||||
|
@ -149,7 +130,7 @@ const bigPictureSortOrder = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getLandscapeItems({landscapeSettings, items, guideIndex = {}}) {
|
module.exports.getLandscapeItems = function({landscapeSettings, items, guideIndex = {}}) {
|
||||||
if (landscapeSettings.isMain) {
|
if (landscapeSettings.isMain) {
|
||||||
const categories = getLandscapeCategories({landscapeSettings, landscape });
|
const categories = getLandscapeCategories({landscapeSettings, landscape });
|
||||||
const itemsMap = groupAndSort(items, bigPictureSortOrder);
|
const itemsMap = groupAndSort(items, bigPictureSortOrder);
|
||||||
|
@ -208,15 +189,13 @@ export function getLandscapeItems({landscapeSettings, items, guideIndex = {}}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const flattenItems = groupedItems => {
|
module.exports.flattenItems = groupedItems => {
|
||||||
return groupedItems.flatMap(group => {
|
return groupedItems.flatMap(group => {
|
||||||
const { items, subcategories } = group
|
const { items, subcategories } = group
|
||||||
return group.hasOwnProperty('items') ? items : subcategories.flatMap(({ items }) => items)
|
return group.hasOwnProperty('items') ? items : subcategories.flatMap(({ items }) => items)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getItemsForExport(params) {
|
module.exports.getItemsForExport = function(params) {
|
||||||
return _.flatten(getGroupedItems(params).map((x) => x.items));
|
return _.flatten(getGroupedItems(params).map((x) => x.items));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getGroupedItems;
|
|
||||||
|
|
|
@ -1,29 +1,35 @@
|
||||||
import path from 'path';
|
const { readJsonFromDist } = require('./readJson');
|
||||||
import fs from 'fs';
|
const { fields } = require("../types/fields");
|
||||||
import settings from 'dist/settings';
|
|
||||||
import fields from "../types/fields";
|
|
||||||
|
|
||||||
export const itemMargin = 3
|
const settings = readJsonFromDist('settings');
|
||||||
export const smallItemWidth = 34
|
|
||||||
export const smallItemHeight = 30
|
/* eslint-disable no-unused-vars */
|
||||||
export const largeItemWidth = 2 * smallItemWidth + itemMargin
|
const itemMargin = module.exports.itemMargin = 3;
|
||||||
export const largeItemHeight = 2 * smallItemHeight + itemMargin
|
const smallItemWidth = module.exports.smallItemWidth = 34;
|
||||||
export const subcategoryMargin = 6
|
const smallItemHeight = module.exports.smallItemHeight = 30;
|
||||||
export const subcategoryTitleHeight = 20
|
const subcategoryMargin = module.exports.subcategoryMargin = 6;
|
||||||
export const dividerWidth = 2
|
const subcategoryTitleHeight = module.exports.subcategoryTitleHeight = 20;
|
||||||
export const categoryBorder = 1
|
const dividerWidth = module.exports.dividerWidth = 2;
|
||||||
export const categoryTitleHeight = 30
|
const categoryBorder = module.exports.categoryBorder = 1;
|
||||||
export const outerPadding = 20
|
const categoryTitleHeight = module.exports.categoryTitleHeight = 30;
|
||||||
export const headerHeight = 40
|
const outerPadding = module.exports.outerPadding = 20;
|
||||||
|
const headerHeight = module.exports.headerHeight = 40;
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
// Check if item is large
|
// Check if item is large
|
||||||
export const isLargeFn = ({ relation, category, member, categoryAttrs }) => {
|
const sizeFn = module.exports.sizeFn = ({ relation, category, member, categoryAttrs }) => {
|
||||||
const relationInfo = fields.relation.valuesMap[relation]
|
const relationInfo = fields.relation.valuesMap[relation]
|
||||||
|
if (!relationInfo) {
|
||||||
|
console.error(`No relation with name ${relation}`);
|
||||||
|
}
|
||||||
|
if (relationInfo.x4) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
if (category === settings.global.membership) {
|
if (category === settings.global.membership) {
|
||||||
const membershipInfo = settings.membership[member];
|
const membershipInfo = settings.membership[member];
|
||||||
return membershipInfo && !!membershipInfo.is_large;
|
return (membershipInfo && !!membershipInfo.is_large) ? 4 : 1;
|
||||||
}
|
}
|
||||||
return !!categoryAttrs.isLarge || !!relationInfo.big_picture_order;
|
return (!!categoryAttrs.isLarge || !!relationInfo.big_picture_order) ? 4 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute if items are large and/or visible.
|
// Compute if items are large and/or visible.
|
||||||
|
@ -32,16 +38,15 @@ export const isLargeFn = ({ relation, category, member, categoryAttrs }) => {
|
||||||
const computeItems = (subcategories, addInfoIcon = false) => {
|
const computeItems = (subcategories, addInfoIcon = false) => {
|
||||||
return subcategories.map(subcategory => {
|
return subcategories.map(subcategory => {
|
||||||
const filteredItems = subcategory.items.reduce((acc, { id }) => ({ ...acc, [id]: true }), {})
|
const filteredItems = subcategory.items.reduce((acc, { id }) => ({ ...acc, [id]: true }), {})
|
||||||
const allItems = subcategory.allItems.map(item => ({ ...item, isLarge: isLargeFn(item), isVisible: filteredItems[item.id] }))
|
const allItems = subcategory.allItems.map(item => ({ ...item, size: sizeFn(item), isVisible: filteredItems[item.id] }))
|
||||||
const itemsCount = allItems.reduce((count, item) => count + (item.isLarge ? 4 : 1), 0) + (addInfoIcon ? 1 : 0)
|
const itemsCount = allItems.reduce((count, item) => count + item.size, 0) + (addInfoIcon ? 1 : 0)
|
||||||
const largeItemsCount = allItems.reduce((count, item) => count + (item.isLarge ? 1 : 0), 0)
|
const largeItemsCount = allItems.reduce((count, item) => count + (item.size === 16 ? 4 : item.size === 4 ? 1 : 0), 0)
|
||||||
|
|
||||||
return { ...subcategory, allItems, itemsCount, largeItemsCount }
|
return { ...subcategory, allItems, itemsCount, largeItemsCount }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate width and height of a given landscape
|
// Calculate width and height of a given landscape
|
||||||
export const calculateSize = landscapeSettings => {
|
module.exports.calculateSize = landscapeSettings => {
|
||||||
const width = Math.max(...landscapeSettings.elements.map(({ left, width }) => left + width))
|
const width = Math.max(...landscapeSettings.elements.map(({ left, width }) => left + width))
|
||||||
const height = Math.max(...landscapeSettings.elements.map(({ top, height }) => top + height))
|
const height = Math.max(...landscapeSettings.elements.map(({ top, height }) => top + height))
|
||||||
const fullscreenWidth = width + 2 * outerPadding
|
const fullscreenWidth = width + 2 * outerPadding
|
||||||
|
@ -51,7 +56,7 @@ export const calculateSize = landscapeSettings => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate each subcategory width and the disposition of its items, assuming fixed padding for each item.
|
// Calculate each subcategory width and the disposition of its items, assuming fixed padding for each item.
|
||||||
const calculateHorizontalFixedWidth = ({ subcategories, maxColumns, maxRows, fitWidth }) => {
|
const calculateHorizontalFixedWidth = ({ subcategories, maxColumns, maxRows }) => {
|
||||||
let availableColumns = maxColumns
|
let availableColumns = maxColumns
|
||||||
|
|
||||||
subcategories.slice(0)
|
subcategories.slice(0)
|
||||||
|
@ -129,10 +134,10 @@ const calculateHorizontalStretch = ({ subcategories, maxWidth, maxHeight }) => {
|
||||||
|
|
||||||
subcategories.forEach(subcategory => subcategory.width = Math.floor(maxWidth * subcategory.columns / totalColumns))
|
subcategories.forEach(subcategory => subcategory.width = Math.floor(maxWidth * subcategory.columns / totalColumns))
|
||||||
|
|
||||||
return subcategories
|
return subcategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const calculateHorizontalCategory = ({ height, width, subcategories, fitWidth, addInfoIcon = false }) => {
|
module.exports.calculateHorizontalCategory = ({ height, width, subcategories, fitWidth, addInfoIcon = false }) => {
|
||||||
const subcategoriesWithCalculations = computeItems(subcategories, addInfoIcon)
|
const subcategoriesWithCalculations = computeItems(subcategories, addInfoIcon)
|
||||||
const maxWidth = width - categoryTitleHeight - categoryBorder - (2 * subcategoryMargin - itemMargin + dividerWidth) * subcategories.length + dividerWidth
|
const maxWidth = width - categoryTitleHeight - categoryBorder - (2 * subcategoryMargin - itemMargin + dividerWidth) * subcategories.length + dividerWidth
|
||||||
const maxHeight = height - 2 * (subcategoryMargin + categoryBorder) + itemMargin - 2 * categoryBorder
|
const maxHeight = height - 2 * (subcategoryMargin + categoryBorder) + itemMargin - 2 * categoryBorder
|
||||||
|
@ -146,7 +151,7 @@ export const calculateHorizontalCategory = ({ height, width, subcategories, fitW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const calculateVerticalCategory = ({ subcategories, fitWidth, width }) => {
|
module.exports.calculateVerticalCategory = ({ subcategories, fitWidth, width }) => {
|
||||||
const subcategoriesWithCalculations = computeItems(subcategories)
|
const subcategoriesWithCalculations = computeItems(subcategories)
|
||||||
const maxColumns = Math.floor((width - 2 * (categoryBorder + itemMargin)) / (smallItemWidth + itemMargin))
|
const maxColumns = Math.floor((width - 2 * (categoryBorder + itemMargin)) / (smallItemWidth + itemMargin))
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
import path from 'path';
|
const { readJsonFromDist } = require('./readJson');
|
||||||
import fs from 'fs';
|
const settings = readJsonFromDist('settings');
|
||||||
import settings from 'dist/settings';
|
|
||||||
|
|
||||||
function calcLandscapeSettingsList(settingsObj) {
|
function calcLandscapeSettingsList(settingsObj) {
|
||||||
return Object.values(settingsObj.big_picture)
|
return Object.values(settingsObj.big_picture)
|
||||||
.sort((a, b) => a.tab_index - b.tab_index)
|
.sort((a, b) => a.tab_index - b.tab_index)
|
||||||
.map(({ url, ...rest }) => {
|
.map(({ url, ...rest }) => {
|
||||||
const basePath = url === 'landscape' ? null : url
|
const basePath = url === 'landscape' ? null : url
|
||||||
const isMain = settingsObj.big_picture.main.url === url
|
const isMain = !rest.category;
|
||||||
return { url, basePath, isMain, ...rest }
|
return { url, basePath, isMain, ...rest }
|
||||||
})
|
})
|
||||||
|
|
||||||
};
|
}
|
||||||
|
|
||||||
// client side version
|
// client side version
|
||||||
export const landscapeSettingsList = calcLandscapeSettingsList(settings);
|
const landscapeSettingsList = module.exports.landscapeSettingsList = calcLandscapeSettingsList(settings);
|
||||||
const landscapeSettingsDict = landscapeSettingsList.reduce((dict, landscapeSettings) => {
|
const landscapeSettingsDict = landscapeSettingsList.reduce((dict, landscapeSettings) => {
|
||||||
dict[landscapeSettings.url] = landscapeSettings;
|
dict[landscapeSettings.url] = landscapeSettings;
|
||||||
return dict;
|
return dict;
|
||||||
}, {})
|
}, {});
|
||||||
|
|
||||||
export const findLandscapeSettings = (url) => {
|
module.exports.findLandscapeSettings = (url) => {
|
||||||
if (url === 'main') {
|
if (url === 'main') {
|
||||||
url = 'landscape';
|
url = 'landscape';
|
||||||
}
|
}
|
||||||
|
@ -28,6 +27,6 @@ export const findLandscapeSettings = (url) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// server side version, with up to date information
|
// server side version, with up to date information
|
||||||
export function getLandscapeSettingsList(settingsObj) {
|
module.exports.getLandscapeSettingsList = function(settingsObj) {
|
||||||
return calcLandscapeSettingsList(settingsObj);
|
return calcLandscapeSettingsList(settingsObj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default function pack(records) {
|
|
||||||
const keys = _.uniq(_.flatten(_.map(records, (x) => _.keys(x))));
|
|
||||||
const compact = _.map( records, (x) => _.map(keys, (key) => x[key] || '!E'));
|
|
||||||
return [keys].concat(compact);
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// allows to read json files from certain directores
|
||||||
|
|
||||||
|
module.exports.readJsonFromProject = function(file) {
|
||||||
|
if (global.lookups) {
|
||||||
|
if (global.lookups[file]) {
|
||||||
|
return global.lookups[file];
|
||||||
|
} else {
|
||||||
|
throw ('Can not read ', file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fullPath = require('path').resolve(process.env.PROJECT_PATH, file + '.json');
|
||||||
|
return JSON.parse(require('fs').readFileSync(fullPath, 'utf-8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.readJsonFromDist = function(file) {
|
||||||
|
if (global.lookups && global.lookups[file]) {
|
||||||
|
return global.lookups[file];
|
||||||
|
}
|
||||||
|
const fullPath = require('path').resolve(process.env.PROJECT_PATH, 'dist', process.env.PROJECT_NAME || '', file + '.json');
|
||||||
|
return JSON.parse(require('fs').readFileSync(fullPath, 'utf-8'));
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
import qs, { stringifyUrl } from 'query-string'
|
const qs = require('query-string');
|
||||||
import fields, { sortOptions } from '../types/fields'
|
|
||||||
import { isArray } from 'lodash'
|
|
||||||
|
|
||||||
const defaultSort = 'name'
|
const { fields, sortOptions } = require('../types/fields');
|
||||||
const defaultGrouping = 'relation'
|
const { isArray } = require('lodash');
|
||||||
|
|
||||||
|
const defaultSort = 'name';
|
||||||
|
const defaultGrouping = 'relation';
|
||||||
|
|
||||||
const compact = obj => {
|
const compact = obj => {
|
||||||
return Object.entries(obj).reduce((result, [key, value]) => {
|
return Object.entries(obj).reduce((result, [key, value]) => {
|
||||||
|
@ -119,7 +120,7 @@ const stringifyParams = (params = {}) => {
|
||||||
...filters
|
...filters
|
||||||
})
|
})
|
||||||
|
|
||||||
return stringifyUrl({ url: `/${path}`, query },
|
return qs.stringifyUrl({ url: `/${path}`, query },
|
||||||
{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true })
|
{ arrayFormat: 'comma', skipNull: true, skipEmptyString: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,4 +148,5 @@ const parseParams = (query) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { stringifyParams, parseParams }
|
module.exports.stringifyParams = stringifyParams;
|
||||||
|
module.exports.parseParams = parseParams;
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import { paramCase } from 'change-case';
|
const { paramCase } = require('change-case');
|
||||||
export default function(x) {
|
let hash = {};
|
||||||
return _.deburr(paramCase(x));
|
module.exports.saneName = function(x) {
|
||||||
|
let result = _.deburr(paramCase(x));
|
||||||
|
if (hash[result] && hash[result] !== x) {
|
||||||
|
result = result + '-2';
|
||||||
|
console.info(`Hash for ${x} is ${result}`);
|
||||||
|
}
|
||||||
|
hash[result] = x;
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default function selectedItemCalculator(groupedItems, selectedItemId, isBigPicture) {
|
|
||||||
const calcItems = function() {
|
|
||||||
if (!isBigPicture) {
|
|
||||||
return groupedItems.flatMap(group => group.items)
|
|
||||||
}
|
|
||||||
// if we are in a big picture mode, we want to allow prev/next button to work only inside a given category
|
|
||||||
const itemsByCategory = groupedItems.map(category => category.subcategories.flatMap(subcategory => subcategory.items))
|
|
||||||
return itemsByCategory.find(items => items.find(item => item.id === selectedItemId)) || []
|
|
||||||
}
|
|
||||||
const items = calcItems();
|
|
||||||
const index = _.findIndex(items, {id: selectedItemId});
|
|
||||||
const item = items[index];
|
|
||||||
const nextItem = items[index + 1];
|
|
||||||
const previousItem = items[index - 1];
|
|
||||||
return {
|
|
||||||
itemInfo: item,
|
|
||||||
hasSelectedItem: !!item,
|
|
||||||
nextItemId: (nextItem || {id: null}).id,
|
|
||||||
previousItemId: (previousItem || {id: null}).id
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,11 +1,5 @@
|
||||||
// this file contains a code which can be used on both server and client side.
|
const _ = require('lodash');
|
||||||
// client side uses this to get a list of items for a big picture
|
module.exports.getLandscapeCategories = ({ landscape, landscapeSettings }) => {
|
||||||
// server side uses this to build a sitemap, so we know which landscape a given
|
|
||||||
// element belongs to
|
|
||||||
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export const getLandscapeCategories = ({ landscape, landscapeSettings }) => {
|
|
||||||
if (landscapeSettings.isMain) {
|
if (landscapeSettings.isMain) {
|
||||||
return landscape.filter( ({ level }) => level === 1).filter((category) => {
|
return landscape.filter( ({ level }) => level === 1).filter((category) => {
|
||||||
return _.find(landscapeSettings.elements, (element) => element.category === category.id);
|
return _.find(landscapeSettings.elements, (element) => element.category === category.id);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function shortRepoName(url) {
|
module.exports.shortRepoName = function(url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function stringOrSpecial(x) {
|
module.exports.stringOrSpecial = function(x) {
|
||||||
if (x === 'true') {
|
if (x === 'true') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import { getItemsForExport, getLandscapeItems } from './itemsCalculator';
|
|
||||||
import { millify } from '../utils/format';
|
|
||||||
import formatNumber from '../utils/formatNumber';
|
|
||||||
import { findLandscapeSettings } from "./landscapeSettings";
|
|
||||||
|
|
||||||
import path from 'path';
|
const { getItemsForExport, getLandscapeItems } = require('./itemsCalculator');
|
||||||
import fs from 'fs';
|
const { millify } = require('../utils/format');
|
||||||
import settings from 'dist/settings';
|
const { formatNumber } = require('../utils/formatNumber');
|
||||||
|
const { findLandscapeSettings } = require("./landscapeSettings");
|
||||||
|
const { readJsonFromDist } = require('./readJson');
|
||||||
|
|
||||||
|
const settings = readJsonFromDist('settings');
|
||||||
|
|
||||||
const getOrganizations = function(params) {
|
const getOrganizations = function(params) {
|
||||||
const filteredItems = getItemsForExport(params);
|
const filteredItems = getItemsForExport(params);
|
||||||
|
@ -46,7 +46,9 @@ const getSummary = function(params) {
|
||||||
const marketCap = settings.global.hide_funding_and_market_cap ? 0 :_.sumBy(organizations, 'marketCap');
|
const marketCap = settings.global.hide_funding_and_market_cap ? 0 :_.sumBy(organizations, 'marketCap');
|
||||||
return { total, stars, funding, marketCap };
|
return { total, stars, funding, marketCap };
|
||||||
}
|
}
|
||||||
export const getSummaryText = function(summary) {
|
module.exports.getSummary = getSummary;
|
||||||
|
|
||||||
|
const getSummaryText = function(summary) {
|
||||||
if (!summary.total) {
|
if (!summary.total) {
|
||||||
return 'There are no cards matching your filters';
|
return 'There are no cards matching your filters';
|
||||||
}
|
}
|
||||||
|
@ -66,4 +68,4 @@ export const getSummaryText = function(summary) {
|
||||||
return `${startText} ${text}.`;
|
return `${startText} ${text}.`;
|
||||||
|
|
||||||
};
|
};
|
||||||
export default getSummary;
|
module.exports.getSummaryText = getSummaryText;
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
export default function unpack(records) {
|
|
||||||
const keys = records[0];
|
|
||||||
const compact = records.slice(1);
|
|
||||||
const result = _.map(compact, (arr) => _.fromPairs(_.map(arr, (el, index) => [keys[index], el]).filter( (x) => x[1] !== '!E')));
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function actualTwitter(node, crunchbaseEntry) {
|
module.exports.actualTwitter = function actualTwitter(node, crunchbaseEntry) {
|
||||||
const twitterUrl = 'twitter' in node ? node.twitter : (crunchbaseEntry || {}).twitter;
|
const twitterUrl = 'twitter' in node ? node.twitter : (crunchbaseEntry || {}).twitter;
|
||||||
|
|
||||||
if (twitterUrl) {
|
if (twitterUrl) {
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
import checkVersion from './checkVersion';
|
const path = require('path');
|
||||||
import { hasFatalErrors, reportFatalErrors } from './fatalErrors';
|
const traverse = require('traverse');
|
||||||
import errorsReporter from './reporter';
|
const _ = require('lodash');
|
||||||
import process from 'process';
|
|
||||||
import path from 'path';
|
const { checkVersion } = require('./checkVersion');
|
||||||
import { projectPath, settings } from './settings';
|
const { hasFatalErrors, reportFatalErrors } = require('./fatalErrors');
|
||||||
import actualTwitter from './actualTwitter';
|
const { errorsReporter } = require('./reporter');
|
||||||
import { extractSavedImageEntries, fetchImageEntries, removeNonReferencedImages } from './fetchImages';
|
const { projectPath, settings } = require('./settings');
|
||||||
import { extractSavedCrunchbaseEntries, fetchCrunchbaseEntries } from './crunchbase';
|
const { actualTwitter } = require('./actualTwitter');
|
||||||
import { fetchGithubEntries } from './fetchGithubStats';
|
const { extractSavedImageEntries, fetchImageEntries, removeNonReferencedImages } = require('./fetchImages');
|
||||||
import { getProcessedRepos, getProcessedReposStartDates } from './repos';
|
const { extractSavedCrunchbaseEntries, fetchCrunchbaseEntries } = require('./crunchbase');
|
||||||
import { fetchStartDateEntries } from './fetchGithubStartDate';
|
const { fetchGithubEntries } = require('./fetchGithubStats');
|
||||||
import { extractSavedTwitterEntries, fetchTwitterEntries } from './twitter';
|
const { getProcessedRepos, getProcessedReposStartDates } = require('./repos');
|
||||||
import {
|
const { fetchStartDateEntries } = require('./fetchGithubStartDate');
|
||||||
|
const { fetchCloEntries } = require('./fetchCloData');
|
||||||
|
const { extractSavedTwitterEntries, fetchTwitterEntries } = require('./twitter');
|
||||||
|
const {
|
||||||
extractSavedBestPracticeEntries,
|
extractSavedBestPracticeEntries,
|
||||||
fetchBestPracticeEntriesWithFullScan,
|
fetchBestPracticeEntriesWithFullScan,
|
||||||
fetchBestPracticeEntriesWithIndividualUrls
|
fetchBestPracticeEntriesWithIndividualUrls
|
||||||
} from './fetchBestPractices';
|
} = require('./fetchBestPractices');
|
||||||
import shortRepoName from '../src/utils/shortRepoName';
|
const { shortRepoName } = require('../src/utils/shortRepoName');
|
||||||
import { updateProcessedLandscape } from "./processedLandscape";
|
const { updateProcessedLandscape } = require("./processedLandscape");
|
||||||
|
|
||||||
const { landscape } = require('./landscape')
|
const { landscape } = require('./landscape')
|
||||||
const traverse = require('traverse');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const { addFatal } = errorsReporter('crunchbase');
|
const { addFatal } = errorsReporter('crunchbase');
|
||||||
|
|
||||||
var useCrunchbaseCache = true;
|
var useCrunchbaseCache = true;
|
||||||
|
@ -30,7 +30,8 @@ var useGithubCache = true;
|
||||||
var useGithubStartDatesCache = true;
|
var useGithubStartDatesCache = true;
|
||||||
var useTwitterCache = true;
|
var useTwitterCache = true;
|
||||||
var useBestPracticesCache = true;
|
var useBestPracticesCache = true;
|
||||||
var key = require('process').env.LEVEL || 'easy';
|
var key = process.env.LEVEL || 'easy';
|
||||||
|
|
||||||
function reportOptions() {
|
function reportOptions() {
|
||||||
console.info(`Running with a level=${key}. Settings:
|
console.info(`Running with a level=${key}. Settings:
|
||||||
Use cached crunchbase data: ${useCrunchbaseCache}
|
Use cached crunchbase data: ${useCrunchbaseCache}
|
||||||
|
@ -43,11 +44,15 @@ function reportOptions() {
|
||||||
}
|
}
|
||||||
if (key.toLowerCase() === 'easy') {
|
if (key.toLowerCase() === 'easy') {
|
||||||
reportOptions();
|
reportOptions();
|
||||||
|
} else if (key.toLowerCase() === 'crunchbase') {
|
||||||
|
|
||||||
|
useCrunchbaseCache = false;
|
||||||
|
reportOptions();
|
||||||
}
|
}
|
||||||
else if (key.toLowerCase() === 'medium') {
|
else if (key.toLowerCase() === 'medium') {
|
||||||
useTwitterCache=false;
|
useTwitterCache=false;
|
||||||
useGithubCache=false;
|
useGithubCache=false;
|
||||||
useCrunchbaseCache=false;
|
useCrunchbaseCache=true;
|
||||||
useBestPracticesCache=false;
|
useBestPracticesCache=false;
|
||||||
reportOptions();
|
reportOptions();
|
||||||
}
|
}
|
||||||
|
@ -164,11 +169,12 @@ async function main() {
|
||||||
|
|
||||||
console.info('Fetching last tweet dates');
|
console.info('Fetching last tweet dates');
|
||||||
const savedTwitterEntries = await extractSavedTwitterEntries();
|
const savedTwitterEntries = await extractSavedTwitterEntries();
|
||||||
const twitterEntries = await fetchTwitterEntries({
|
|
||||||
cache: savedTwitterEntries,
|
// const twitterEntries = await fetchTwitterEntries({
|
||||||
preferCache: useTwitterCache,
|
// cache: savedTwitterEntries,
|
||||||
crunchbaseEntries: crunchbaseEntries
|
// preferCache: useTwitterCache,
|
||||||
});
|
// crunchbaseEntries: crunchbaseEntries
|
||||||
|
// });
|
||||||
|
|
||||||
if (hasFatalErrors()) {
|
if (hasFatalErrors()) {
|
||||||
console.info('Reporting fatal errors');
|
console.info('Reporting fatal errors');
|
||||||
|
@ -184,6 +190,10 @@ async function main() {
|
||||||
cache: savedBestPracticeEntries,
|
cache: savedBestPracticeEntries,
|
||||||
preferCache: useBestPracticesCache
|
preferCache: useBestPracticesCache
|
||||||
});
|
});
|
||||||
|
require('fs').writeFileSync('/tmp/bp.json', JSON.stringify(bestPracticeEntries, null, 4));
|
||||||
|
|
||||||
|
console.info('Fetching CLOMonitor data');
|
||||||
|
const cloEntries = await fetchCloEntries();
|
||||||
|
|
||||||
const tree = traverse(landscape);
|
const tree = traverse(landscape);
|
||||||
console.info('Processing the tree');
|
console.info('Processing the tree');
|
||||||
|
@ -309,10 +319,9 @@ async function main() {
|
||||||
}
|
}
|
||||||
node.best_practice_data = bestPracticeEntry;
|
node.best_practice_data = bestPracticeEntry;
|
||||||
delete node.best_practice_data.repo_url;
|
delete node.best_practice_data.repo_url;
|
||||||
// twitter
|
//twitter
|
||||||
const twitter = actualTwitter(node, node.crunchbase_data);
|
const twitter = actualTwitter(node, node.crunchbase_data);
|
||||||
|
const twitterEntry = _.clone(_.find(savedTwitterEntries, {
|
||||||
const twitterEntry = _.clone(_.find(twitterEntries, {
|
|
||||||
url: twitter
|
url: twitter
|
||||||
}));
|
}));
|
||||||
if (twitterEntry) {
|
if (twitterEntry) {
|
||||||
|
@ -320,6 +329,11 @@ async function main() {
|
||||||
delete twitterEntry.url;
|
delete twitterEntry.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cloEntry = _.clone(_.find(cloEntries, { clomonitor_name: node.extra?.clomonitor_name }));
|
||||||
|
// svg clomonitor
|
||||||
|
if (cloEntry) {
|
||||||
|
node.extra.clomonitor_svg = cloEntry.svg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import path from 'path';
|
const path = require('path');
|
||||||
import { writeFileSync } from 'fs'
|
const { writeFileSync } = require('fs');
|
||||||
import { distPath, settings } from './settings';
|
const { distPath, settings } = require('./settings');
|
||||||
|
|
||||||
const isMainBranch = process.env.PULL_REQUEST !== 'true'
|
const isMainBranch = process.env.PULL_REQUEST !== 'true'
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { env } from 'process';
|
const { env } = require('process');
|
||||||
import { stringify, parse } from 'query-string';
|
const { stringify, parse } = require('query-string');
|
||||||
import axios from 'axios'
|
const axios = require('axios');
|
||||||
import OAuth1 from 'oauth-1.0a'
|
const OAuth1 = require('oauth-1.0a');
|
||||||
import crypto from 'crypto'
|
const crypto = require('crypto');
|
||||||
import _ from 'lodash'
|
const _ = require('lodash');
|
||||||
|
|
||||||
['GITHUB_KEY', 'TWITTER_KEYS'].forEach((key) => {
|
['GITHUB_KEY', 'TWITTER_KEYS'].forEach((key) => {
|
||||||
if (!env[key]) {
|
if (!env[key]) {
|
||||||
|
@ -62,9 +62,12 @@ const requestWithRetry = async ({ attempts = maxAttempts, resolveWithFullRespons
|
||||||
const response = await axios(rest);
|
const response = await axios(rest);
|
||||||
return resolveWithFullResponse ? response : response.data
|
return resolveWithFullResponse ? response : response.data
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
|
||||||
const { response = {}, ...error } = ex
|
const { response = {}, ...error } = ex
|
||||||
const { status } = response
|
const { status } = response
|
||||||
|
|
||||||
|
const isGithubIssue = (response?.data?.message || '').indexOf('is too large to list') !== -1;
|
||||||
|
|
||||||
const message = [
|
const message = [
|
||||||
`Attempt #${maxAttempts - attempts + 1}`,
|
`Attempt #${maxAttempts - attempts + 1}`,
|
||||||
`(Status Code: ${status || error.code})`,
|
`(Status Code: ${status || error.code})`,
|
||||||
|
@ -72,10 +75,12 @@ const requestWithRetry = async ({ attempts = maxAttempts, resolveWithFullRespons
|
||||||
].join(' ')
|
].join(' ')
|
||||||
if (key === keys[keys.length - 1]) {
|
if (key === keys[keys.length - 1]) {
|
||||||
console.info(message);
|
console.info(message);
|
||||||
|
} else {
|
||||||
|
console.info(`Failed to use key #${keys.indexOf(key)} of ${keys.length}`);
|
||||||
}
|
}
|
||||||
const rateLimited = retryStatuses.includes(status)
|
const rateLimited = retryStatuses.includes(status)
|
||||||
const dnsError = error.code === 'ENOTFOUND' && error.syscall === 'getaddrinfo'
|
const dnsError = error.code === 'ENOTFOUND' && error.syscall === 'getaddrinfo'
|
||||||
if (attempts <= 0 || (!rateLimited && !dnsError)) {
|
if (attempts <= 0 || (!rateLimited && !dnsError) || isGithubIssue) {
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
lastEx = ex;
|
lastEx = ex;
|
||||||
|
@ -117,15 +122,15 @@ const ApiClient = ({ baseURL, applyKey, keys, defaultOptions = {}, defaultParams
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CrunchbaseClient = ApiClient({
|
module.exports.CrunchbaseClient = ApiClient({
|
||||||
baseURL: 'https://api.crunchbase.com/api/v4',
|
baseURL: 'https://api.crunchbase.com/api/v4',
|
||||||
defaultParams: { user_key: env.CRUNCHBASE_KEY_4 },
|
defaultParams: { user_key: env.CRUNCHBASE_KEY_4 },
|
||||||
defaultOptions: { followRedirect: true, maxRedirects: 5, timeout: 10 * 1000 }
|
defaultOptions: { followRedirect: true, maxRedirects: 5, timeout: 10 * 1000 }
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GithubClient = ApiClient({
|
module.exports.GithubClient = ApiClient({
|
||||||
baseURL: 'https://api.github.com',
|
baseURL: 'https://api.github.com',
|
||||||
retryStatuses: [403], // Github returns 403 when rate limiting.
|
retryStatuses: [401, 403], // Github returns 403 when rate limiting.
|
||||||
delayFn: error => {
|
delayFn: error => {
|
||||||
const rateLimitRemaining = parseInt(_.get(error, ['response', 'headers', 'x-ratelimit-remaining'], 1))
|
const rateLimitRemaining = parseInt(_.get(error, ['response', 'headers', 'x-ratelimit-remaining'], 1))
|
||||||
const rateLimitReset = parseInt(_.get(error, ['response', 'headers', 'x-ratelimit-reset'], 1)) * 1000
|
const rateLimitReset = parseInt(_.get(error, ['response', 'headers', 'x-ratelimit-reset'], 1)) * 1000
|
||||||
|
@ -150,7 +155,7 @@ export const GithubClient = ApiClient({
|
||||||
|
|
||||||
const [consumerKey, consumerSecret, accessTokenKey, accessTokenSecret] = (env.TWITTER_KEYS || '').split(',');
|
const [consumerKey, consumerSecret, accessTokenKey, accessTokenSecret] = (env.TWITTER_KEYS || '').split(',');
|
||||||
|
|
||||||
export const TwitterClient = ApiClient({
|
module.exports.TwitterClient = ApiClient({
|
||||||
baseURL: 'https://api.twitter.com/1.1',
|
baseURL: 'https://api.twitter.com/1.1',
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
oauth: {
|
oauth: {
|
||||||
|
@ -162,6 +167,6 @@ export const TwitterClient = ApiClient({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const YahooFinanceClient = ApiClient({
|
module.exports.YahooFinanceClient = ApiClient({
|
||||||
baseURL: 'https://query2.finance.yahoo.com',
|
baseURL: 'https://query2.finance.yahoo.com',
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
//---------------------------------------------------------------------
|
|
||||||
// This is a fix for jest handling static assets like imported images
|
|
||||||
// when running tests. It's configured in jest section of package.json
|
|
||||||
//
|
|
||||||
// See:
|
|
||||||
// https://github.com/facebook/jest/issues/2663#issuecomment-317109798
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
process(src, filename /*, config, options */) {
|
|
||||||
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,16 +1,17 @@
|
||||||
import { projectPath } from './settings';
|
const autoCropSvg = require('svg-autocrop');
|
||||||
import autoCropSvg from 'svg-autocrop';
|
const path = require('path');
|
||||||
import path from 'path';
|
const fs = require('fs');
|
||||||
import fs from 'fs';
|
|
||||||
|
const { projectPath } = require('./settings');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const files = require('fs').readdirSync(path.resolve(projectPath, 'images'));
|
const files = fs.readdirSync(path.resolve(projectPath, 'images'));
|
||||||
const svgFiles = files.filter((x) => x.match(/\.svg$/));
|
const svgFiles = files.filter((x) => x.match(/\.svg$/));
|
||||||
for (const file of svgFiles) {
|
for (const file of svgFiles) {
|
||||||
const fullPath = path.resolve(projectPath,'images', file);
|
const fullPath = path.resolve(projectPath,'images', file);
|
||||||
const content = require('fs').readFileSync(fullPath, 'utf-8');
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
const processed = await autoCropSvg(content);
|
const processed = await autoCropSvg(content);
|
||||||
require('fs').writeFileSync(fullPath, processed);
|
fs.writeFileSync(fullPath, processed);
|
||||||
console.info('Optimized an svg image: ', fullPath);
|
console.info('Optimized an svg image: ', fullPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import { TwitterClient } from './apiClients';
|
|
||||||
import { settings } from './settings';
|
const { TwitterClient } = require('./apiClients');
|
||||||
import { updateProcessedLandscape } from "./processedLandscape";
|
const { settings } = require('./settings');
|
||||||
|
const { updateProcessedLandscape } = require("./processedLandscape");
|
||||||
|
|
||||||
// we need to know a latest since_id, otherwise we can only expect
|
// we need to know a latest since_id, otherwise we can only expect
|
||||||
async function getLatestTweets(sinceId) {
|
async function getLatestTweets(sinceId) {
|
||||||
let count = 0;
|
|
||||||
async function getTweets(maxId) {
|
async function getTweets(maxId) {
|
||||||
const params = {q: settings.twitter.search, count: 100, max_id: maxId, since_id: sinceId};
|
const params = {q: settings.twitter.search, count: 100, max_id: maxId, since_id: sinceId};
|
||||||
const result = await TwitterClient.request({ path: 'search/tweets.json', params });
|
const result = await TwitterClient.request({ path: 'search/tweets.json', params });
|
||||||
const withoutLastId = result.statuses.filter( (x) => x.id_str !== maxId);
|
const withoutLastId = result.statuses.filter( (x) => x.id_str !== maxId);
|
||||||
count += withoutLastId.length;
|
|
||||||
if (withoutLastId.length === 0) {
|
if (withoutLastId.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import './suppressAnnoyingWarnings';
|
const Promise = require('bluebird');
|
||||||
import Promise from 'bluebird';
|
const puppeteer = require('puppeteer');
|
||||||
import { landscapeSettingsList } from '../src/utils/landscapeSettings'
|
|
||||||
import { setFatalError, reportFatalErrors } from './fatalErrors';
|
require('./suppressAnnoyingWarnings');
|
||||||
import { appUrl } from './distSettings'
|
const { landscapeSettingsList } = require('../src/utils/landscapeSettings');
|
||||||
|
const { setFatalError, reportFatalErrors } = require('./fatalErrors');
|
||||||
|
const { appUrl } = require('./distSettings');
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const puppeteer = require('puppeteer');
|
|
||||||
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
|
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
var hasErrors = false;
|
var hasErrors = false;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { exec } from 'child_process'
|
const { exec } = require('child_process');
|
||||||
import { writeFileSync, unlinkSync } from 'fs'
|
const colors = require('colors');
|
||||||
import colors from 'colors'
|
const Promise = require('bluebird');
|
||||||
import Promise from 'bluebird'
|
const traverse = require('traverse');
|
||||||
import traverse from 'traverse'
|
|
||||||
import { landscape, saveLandscape } from './landscape'
|
const { landscape, saveLandscape } = require('./landscape');
|
||||||
import { updateProcessedLandscape } from './processedLandscape'
|
const { updateProcessedLandscape } = require('./processedLandscape');
|
||||||
import errorsReporter from './reporter';
|
const { errorsReporter } = require('./reporter');
|
||||||
|
|
||||||
const { addError } = errorsReporter('link');
|
const { addError } = errorsReporter('link');
|
||||||
|
|
||||||
|
@ -35,11 +35,10 @@ async function checkViaPuppeteer(url, remainingAttempts = 3) {
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
page.setDefaultNavigationTimeout(120 * 1000);
|
page.setDefaultNavigationTimeout(120 * 1000);
|
||||||
let result = null;
|
|
||||||
try {
|
try {
|
||||||
await page.goto(url);
|
await page.goto(url);
|
||||||
await Promise.delay(5 * 1000);
|
await Promise.delay(5 * 1000);
|
||||||
const newUrl = await page.evaluate ( (x) => window.location.href );
|
const newUrl = await page.evaluate ( () => window.location.href );
|
||||||
await browser.close();
|
await browser.close();
|
||||||
const withoutTrailingSlash = (x) => x.replace(/#(.*)/, '').replace(/\/$/, '');
|
const withoutTrailingSlash = (x) => x.replace(/#(.*)/, '').replace(/\/$/, '');
|
||||||
if (withoutTrailingSlash(newUrl) === withoutTrailingSlash(url)) {
|
if (withoutTrailingSlash(newUrl) === withoutTrailingSlash(url)) {
|
||||||
|
@ -57,7 +56,7 @@ async function checkViaPuppeteer(url, remainingAttempts = 3) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkUrl = (url, attempt = 1) => {
|
const checkUrl = module.exports.checkUrl = (url, attempt = 1) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const curlOptions = [
|
const curlOptions = [
|
||||||
'--fail',
|
'--fail',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import axios from 'axios'
|
const axios = require('axios');
|
||||||
|
|
||||||
export default async function check() {
|
module.exports.checkVersion = async function() {
|
||||||
try {
|
try {
|
||||||
const { data:result } = await axios({
|
const { data:result } = await axios({
|
||||||
url: `https://api.github.com/repos/cncf/landscapeapp/branches/master`,
|
url: `https://api.github.com/repos/cncf/landscapeapp/branches/master`,
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import path from 'path';
|
const path = require('path');
|
||||||
import Promise from 'bluebird';
|
|
||||||
import { projectPath, settings } from './settings';
|
|
||||||
import { dump } from './yaml';
|
|
||||||
|
|
||||||
import { hasFatalErrors, setFatalError, reportFatalErrors } from './fatalErrors';
|
const { projectPath } = require('./settings');
|
||||||
|
const { dump } = require('./yaml');
|
||||||
|
const { hasFatalErrors, setFatalError, reportFatalErrors } = require('./fatalErrors');
|
||||||
|
|
||||||
function hasNonAscii(str) {
|
function hasNonAscii(str) {
|
||||||
return ! /^[\x00-\x7F]*$/.test(str);
|
return ! /^[\x00-\x7F]*$/.test(str);
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
import colors from 'colors';
|
const colors = require('colors');
|
||||||
import Promise from 'bluebird'
|
const Promise = require('bluebird');
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import ensureHttps from './ensureHttps';
|
const path = require('path');
|
||||||
import errorsReporter from './reporter';
|
const debug = require('debug')('cb');
|
||||||
import { projectPath } from './settings';
|
|
||||||
import path from 'path';
|
const { ensureHttps } = require('./ensureHttps');
|
||||||
import makeReporter from './progressReporter';
|
const { errorsReporter } = require('./reporter');
|
||||||
|
const { projectPath } = require('./settings');
|
||||||
|
const { makeReporter } = require('./progressReporter');
|
||||||
|
const { CrunchbaseClient, YahooFinanceClient } = require('./apiClients');
|
||||||
|
|
||||||
const error = colors.red;
|
const error = colors.red;
|
||||||
const fatal = (x) => colors.red(colors.inverse(x));
|
const fatal = (x) => colors.red(colors.inverse(x));
|
||||||
const cacheMiss = colors.green;
|
const cacheMiss = colors.green;
|
||||||
const debug = require('debug')('cb');
|
|
||||||
import { CrunchbaseClient, YahooFinanceClient } from './apiClients';
|
|
||||||
|
|
||||||
const { addError, addFatal } = errorsReporter('crunchbase');
|
const { addError, addFatal } = errorsReporter('crunchbase');
|
||||||
|
|
||||||
const EXCHANGE_SUFFIXES = {
|
const EXCHANGE_SUFFIXES = {
|
||||||
'ams': 'AS', // Amsterdam
|
'ams': 'AS', // Amsterdam
|
||||||
'bit': 'MI', // Milan
|
'bit': 'MI', // Milan
|
||||||
|
'bme': 'MC', // Madrid
|
||||||
'epa': 'PA', // Paris
|
'epa': 'PA', // Paris
|
||||||
'etr': 'DE', // XETRA
|
'etr': 'DE', // XETRA
|
||||||
'fra': 'F', // Frankfurt
|
'fra': 'F', // Frankfurt
|
||||||
|
@ -41,7 +43,7 @@ const getSymbolWithSuffix = (symbol, exchange) => {
|
||||||
return [symbol, exchangeSuffix].filter(_ => _).join('.')
|
return [symbol, exchangeSuffix].filter(_ => _).join('.')
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCrunchbaseOrganizationsList() {
|
const getCrunchbaseOrganizationsList = module.exports.getCrunchbaseOrganizationsList = async function() {
|
||||||
const traverse = require('traverse');
|
const traverse = require('traverse');
|
||||||
const source = require('js-yaml').load(require('fs').readFileSync(path.resolve(projectPath, 'landscape.yml')));
|
const source = require('js-yaml').load(require('fs').readFileSync(path.resolve(projectPath, 'landscape.yml')));
|
||||||
var organizations = [];
|
var organizations = [];
|
||||||
|
@ -65,7 +67,7 @@ export async function getCrunchbaseOrganizationsList() {
|
||||||
return _.orderBy(_.uniq(organizations), 'name');
|
return _.orderBy(_.uniq(organizations), 'name');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractSavedCrunchbaseEntries() {
|
module.exports.extractSavedCrunchbaseEntries = async function() {
|
||||||
const traverse = require('traverse');
|
const traverse = require('traverse');
|
||||||
let source = [];
|
let source = [];
|
||||||
try {
|
try {
|
||||||
|
@ -134,19 +136,24 @@ const fetchCrunchbaseOrganization = async id => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchData(name) {
|
const fetchData = module.exports.fetchData = async function(name) {
|
||||||
const result = await fetchCrunchbaseOrganization(name)
|
const result = await fetchCrunchbaseOrganization(name)
|
||||||
const mapAcquisitions = function(a) {
|
const mapAcquisitions = function(a) {
|
||||||
const result = {
|
let result1;
|
||||||
date: a.announced_on.value,
|
try {
|
||||||
acquiree: a.acquiree_identifier.value,
|
result1 = {
|
||||||
|
date: a.announced_on.value,
|
||||||
|
acquiree: a.acquiree_identifier.value,
|
||||||
|
}
|
||||||
|
if (a.price) {
|
||||||
|
result.price = a.price.value_usd
|
||||||
|
}
|
||||||
|
} catch(ex) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if (a.price) {
|
return result1;
|
||||||
result.price = a.price.value_usd
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
let acquisitions = result.cards.acquiree_acquisitions.map(mapAcquisitions);
|
let acquisitions = result.cards.acquiree_acquisitions.map(mapAcquisitions).filter( (x) => !!x);
|
||||||
const limit = 100;
|
const limit = 100;
|
||||||
let lastPage = result;
|
let lastPage = result;
|
||||||
while (lastPage.cards.acquiree_acquisitions.length === limit) {
|
while (lastPage.cards.acquiree_acquisitions.length === limit) {
|
||||||
|
@ -164,7 +171,8 @@ export async function fetchData(name) {
|
||||||
const parentOrganization = lastOrganization.cards.parent_organization[0].identifier.permalink
|
const parentOrganization = lastOrganization.cards.parent_organization[0].identifier.permalink
|
||||||
if (parents.map(p => p.identifier.permalink).includes(parentOrganization)) {
|
if (parents.map(p => p.identifier.permalink).includes(parentOrganization)) {
|
||||||
const { permalink } = lastOrganization.properties.identifier
|
const { permalink } = lastOrganization.properties.identifier
|
||||||
throw new Error(`Circular dependency detected: ${permalink} and ${parentOrganization} are parents of each other`)
|
console.info(`Circular dependency detected: ${permalink} and ${parentOrganization} are parents of each other`)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
lastOrganization = await fetchCrunchbaseOrganization(parentOrganization)
|
lastOrganization = await fetchCrunchbaseOrganization(parentOrganization)
|
||||||
parents.push({ ...lastOrganization.properties, delisted: isDelisted(lastOrganization) })
|
parents.push({ ...lastOrganization.properties, delisted: isDelisted(lastOrganization) })
|
||||||
|
@ -177,6 +185,9 @@ export async function fetchData(name) {
|
||||||
const totalFunding = firstWithTotalFunding ? + firstWithTotalFunding.funding_total.value_usd.toFixed() : undefined;
|
const totalFunding = firstWithTotalFunding ? + firstWithTotalFunding.funding_total.value_usd.toFixed() : undefined;
|
||||||
|
|
||||||
const getAddressPart = function(part) {
|
const getAddressPart = function(part) {
|
||||||
|
if (!result.cards.headquarters_address[0]) {
|
||||||
|
return " N/A";
|
||||||
|
}
|
||||||
return (result.cards.headquarters_address[0].location_identifiers.filter( (x) => x.location_type === part)[0] || {}).value
|
return (result.cards.headquarters_address[0].location_identifiers.filter( (x) => x.location_type === part)[0] || {}).value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,10 +202,6 @@ export async function fetchData(name) {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (!result.cards.headquarters_address[0]) {
|
|
||||||
return 'no address';
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: result.properties.name,
|
name: result.properties.name,
|
||||||
description: result.properties.short_description,
|
description: result.properties.short_description,
|
||||||
|
@ -216,7 +223,7 @@ export async function fetchData(name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCrunchbaseEntries({cache, preferCache}) {
|
module.exports.fetchCrunchbaseEntries = async function({cache, preferCache}) {
|
||||||
// console.info(organizations);
|
// console.info(organizations);
|
||||||
// console.info(_.find(organizations, {name: 'foreman'}));
|
// console.info(_.find(organizations, {name: 'foreman'}));
|
||||||
const reporter = makeReporter();
|
const reporter = makeReporter();
|
||||||
|
@ -236,11 +243,6 @@ export async function fetchCrunchbaseEntries({cache, preferCache}) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await fetchData(c.name);
|
const result = await fetchData(c.name);
|
||||||
if (result === 'no address') {
|
|
||||||
fatalErrors.push(`no headquarter addresses for ${c.name} at ${c.crunchbase}`);
|
|
||||||
reporter.write(fatal("F"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
url: c.crunchbase,
|
url: c.crunchbase,
|
||||||
|
@ -255,8 +257,12 @@ export async function fetchCrunchbaseEntries({cache, preferCache}) {
|
||||||
if (!(c.ticker === null) && (entry.ticker || c.ticker)) {
|
if (!(c.ticker === null) && (entry.ticker || c.ticker)) {
|
||||||
// console.info('need to get a ticker?');
|
// console.info('need to get a ticker?');
|
||||||
entry.effective_ticker = c.ticker || entry.ticker;
|
entry.effective_ticker = c.ticker || entry.ticker;
|
||||||
entry.market_cap = await getMarketCap(entry.effective_ticker, entry.stockExchange);
|
try {
|
||||||
entry.kind = 'market_cap';
|
entry.market_cap = await getMarketCap(entry.effective_ticker, entry.stockExchange);
|
||||||
|
entry.kind = 'market_cap';
|
||||||
|
} catch(ex) {
|
||||||
|
console.info(`Skipping market cap calculation`);
|
||||||
|
}
|
||||||
} else if (entry.funding) {
|
} else if (entry.funding) {
|
||||||
entry.kind = 'funding';
|
entry.kind = 'funding';
|
||||||
} else {
|
} else {
|
||||||
|
@ -266,6 +272,7 @@ export async function fetchCrunchbaseEntries({cache, preferCache}) {
|
||||||
return entry;
|
return entry;
|
||||||
// console.info(entry);
|
// console.info(entry);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
console.info(ex);
|
||||||
if (cachedEntry) {
|
if (cachedEntry) {
|
||||||
errors.push(`Using cached entry, because can not fetch: ${c.name} ` + ex.message.substring(0, 200));
|
errors.push(`Using cached entry, because can not fetch: ${c.name} ` + ex.message.substring(0, 200));
|
||||||
reporter.write(error("E"));
|
reporter.write(error("E"));
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
const port = process.env.PORT || '4000';
|
const port = process.env.PORT || '4000';
|
||||||
export const pathPrefix = process.env.PROJECT_NAME ? `/${process.env.PROJECT_NAME}` : ''
|
const pathPrefix = module.exports.pathPrefix = process.env.PROJECT_NAME ? `/${process.env.PROJECT_NAME}` : '';
|
||||||
export const appUrl = `http://localhost:${port}${pathPrefix}`
|
module.exports.appUrl = `http://localhost:${port}${pathPrefix}`;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function(x) {
|
module.exports.ensureHttps = function(x) {
|
||||||
if (!x) {
|
if (!x) {
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,19 @@
|
||||||
// Netlify does not allow nested directories for functions
|
// Netlify does not allow nested directories for functions
|
||||||
// so if we're building the preview for landscapeapp we will prefix function names with `$landscapeName--`
|
// so if we're building the preview for landscapeapp we will prefix function names with `$landscapeName--`
|
||||||
|
|
||||||
import path from 'path';
|
const path = require('path');
|
||||||
import { distPath, projectPath } from './settings';
|
const fs = require('fs');
|
||||||
import fs from 'fs'
|
const ncc = require('@vercel/ncc');
|
||||||
import ncc from '@vercel/ncc'
|
|
||||||
|
|
||||||
import items from 'dist/data/items';
|
const { distPath } = require('./settings');
|
||||||
import itemsExport from 'dist/data/items-export';
|
const { readJsonFromDist, readJsonFromProject } = require('../src/utils/readJson');
|
||||||
import settings from 'dist/settings';
|
const { settings } = require('./settings');
|
||||||
import lookup from 'project/lookup';
|
|
||||||
|
|
||||||
const { PROJECT_NAME, PROJECT_PATH } = process.env
|
const items = readJsonFromDist('data/items');
|
||||||
|
const itemsExport = readJsonFromDist('data/items-export');
|
||||||
|
const lookup = readJsonFromProject('lookup');
|
||||||
|
|
||||||
|
const { PROJECT_NAME } = process.env
|
||||||
|
|
||||||
const destFolder = path.resolve(distPath, 'functions');
|
const destFolder = path.resolve(distPath, 'functions');
|
||||||
const srcFolder = `${process.env.PWD}/src/api`;
|
const srcFolder = `${process.env.PWD}/src/api`;
|
||||||
|
@ -27,30 +29,31 @@ async function main() {
|
||||||
const destFile = [PROJECT_NAME, file].filter(_ => _).join('--')
|
const destFile = [PROJECT_NAME, file].filter(_ => _).join('--')
|
||||||
const { code } = await ncc(`${srcFolder}/${file}`);
|
const { code } = await ncc(`${srcFolder}/${file}`);
|
||||||
|
|
||||||
code = code.replace(
|
const finalCode = code
|
||||||
'module.exports = eval("require")("dist/data/items");',
|
.replaceAll(`readJsonFromDist('settings')`, JSON.stringify(settings))
|
||||||
`module.exports = ${JSON.stringify(items)};`
|
.replaceAll(`readJsonFromDist('data/items')`, JSON.stringify(items))
|
||||||
)
|
.replaceAll(`readJsonFromDist('data/items-export')`, JSON.stringify(itemsExport))
|
||||||
code = code.replace(
|
.replaceAll(`readJsonFromProject('lookup')`, JSON.stringify(lookup))
|
||||||
'module.exports = eval("require")("dist/data/items-export");',
|
|
||||||
`module.exports = ${JSON.stringify(itemsExport)};`
|
|
||||||
)
|
|
||||||
code = code.replace(
|
|
||||||
'module.exports = eval("require")("dist/settings");',
|
|
||||||
`module.exports = ${JSON.stringify(settings)};`
|
|
||||||
)
|
|
||||||
code = code.replace(
|
|
||||||
'module.exports = eval("require")("project/lookup");',
|
|
||||||
`module.exports = ${JSON.stringify(lookup)};`
|
|
||||||
)
|
|
||||||
|
|
||||||
fs.writeFileSync(`${destFolder}/${destFile}`, code)
|
fs.writeFileSync(`${destFolder}/${destFile}`, finalCode)
|
||||||
if (code.includes('eval("')) {
|
if (finalCode.includes('eval("')) {
|
||||||
console.info('forgot to embed a module');
|
console.info('forgot to embed a module: eval detected');
|
||||||
|
console.info(file);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (finalCode.includes('readJsonFromDist(')) {
|
||||||
|
console.info('readJsonFromDist() detected');
|
||||||
|
console.info(file);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (finalCode.includes('readJsonFromProject(')) {
|
||||||
|
console.info('readJsonFromProject() detected');
|
||||||
|
console.info(file);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch(function(ex) {
|
main().catch(function(ex) {
|
||||||
console.info(ex);
|
console.info(ex);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
import _ from 'lodash';
|
const _ = require('lodash');
|
||||||
import axios from 'axios'
|
const axios = require('axios');
|
||||||
import errorsReporter, { getMessages } from './reporter';
|
|
||||||
|
const { errorsReporter, getMessages } = require('./reporter');
|
||||||
const { addFatal } = errorsReporter('general');
|
const { addFatal } = errorsReporter('general');
|
||||||
|
|
||||||
export function hasFatalErrors() {
|
module.exports.hasFatalErrors = function() {
|
||||||
return getMessages().filter( (x) => x.type === 'fatal') > 0;
|
return getMessages().filter( (x) => x.type === 'fatal') > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFatalError(errorText) {
|
const setFatalError = module.exports.setFatalError = function(errorText) {
|
||||||
addFatal(errorText);
|
addFatal(errorText);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reportFatalErrors() {
|
const reportFatalErrors = module.exports.reportFatalErrors = async function() {
|
||||||
if (!process.env.GITHUB_TOKEN) {
|
if (!process.env.GITHUB_TOKEN) {
|
||||||
console.info(`Can not report fatal errors, GITHUB_TOKEN not provided`);
|
console.info(`Can not report fatal errors, GITHUB_TOKEN not provided`);
|
||||||
return;
|
return;
|
||||||
|
@ -48,7 +49,8 @@ export async function reportFatalErrors() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
fatalErrors = ['FATAL: <div> *b* </div> error number 1', 'FATAL: error number 2'];
|
setFatalError('FATAL: <div> *b* </div> error number 1');
|
||||||
|
setFatalError('FATAL: error number 2');
|
||||||
await reportFatalErrors();
|
await reportFatalErrors();
|
||||||
}
|
}
|
||||||
// uncomment and set env vars to debug
|
// uncomment and set env vars to debug
|
||||||
|
|