diff --git a/craco.config.js b/craco.config.js
new file mode 100644
index 0000000..43f7fbc
--- /dev/null
+++ b/craco.config.js
@@ -0,0 +1,10 @@
+module.exports = {
+ webpack: {
+ configure: {
+ output: {
+ // I need "this" for workerize-loader
+ globalObject: "this"
+ }
+ }
+ }
+};
diff --git a/package.json b/package.json
index 75b6726..136bf87 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"repository": "pomber/git-history",
"private": true,
"dependencies": {
+ "@craco/craco": "^3.5.0",
"diff": "^4.0.1",
"js-base64": "^2.5.1",
"netlify-auth-providers": "^1.0.0-alpha5",
@@ -12,11 +13,12 @@
"react-dom": "^16.8.1",
"react-scripts": "2.1.3",
"react-swipeable": "^4.3.2",
- "react-use": "^5.2.2"
+ "react-use": "^5.2.2",
+ "workerize-loader": "^1.0.4"
},
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
+ "start": "craco start",
+ "build": "craco build",
"format": "prettier --write \"**/*.{js,jsx,md,json,html,css,yml}\" --ignore-path .gitignore",
"test-prettier": "prettier --check \"**/*.{js,jsx,md,json,html,css,yml}\" --ignore-path .gitignore",
"test-cra": "react-scripts test",
diff --git a/src/animation.js b/src/animation.js
index f173867..1fe69d8 100644
--- a/src/animation.js
+++ b/src/animation.js
@@ -1,8 +1,7 @@
+/* eslint-disable */
import { createAnimation, Stagger } from "./airframe/airframe";
import easing from "./airframe/easing";
-/* eslint-disable */
-
const dx = 250;
/* @jsx createAnimation */
diff --git a/src/app-helpers.js b/src/app-helpers.js
index d972037..31dc66a 100644
--- a/src/app-helpers.js
+++ b/src/app-helpers.js
@@ -1,5 +1,4 @@
-import React, { useState, useEffect } from "react";
-import { getLanguage, loadLanguage } from "./language-detector";
+import React, { useEffect } from "react";
export function Center({ children }) {
return (
@@ -68,41 +67,6 @@ export function Error({ error, gitProvider }) {
);
}
-export function useLoader(promiseFactory, deps) {
- const [state, setState] = useState({
- data: null,
- loading: true,
- error: null
- });
-
- useEffect(() => {
- promiseFactory()
- .then(data => {
- setState({
- data,
- loading: false,
- error: false
- });
- })
- .catch(error => {
- setState({
- loading: false,
- error
- });
- });
- }, deps);
-
- return [state.data, state.loading, state.error];
-}
-
-export function useLanguageLoader(path) {
- return useLoader(async () => {
- const lang = getLanguage(path);
- await loadLanguage(lang);
- return lang;
- }, [path]);
-}
-
export function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
diff --git a/src/app.js b/src/app.js
index 2e2399b..37a3c65 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,12 +1,7 @@
import React, { useState, useEffect } from "react";
import History from "./history";
import Landing from "./landing";
-import {
- useLanguageLoader,
- useDocumentTitle,
- Loading,
- Error
-} from "./app-helpers";
+import { useDocumentTitle, Loading, Error } from "./app-helpers";
import getGitProvider from "./git-providers/providers";
export default function App() {
@@ -25,31 +20,32 @@ function InnerApp({ gitProvider }) {
useDocumentTitle(`Git History - ${fileName}`);
- const [commits, commitsLoading, commitsError, loadMore] = useCommitsLoader(
+ const [versions, loading, error, loadMore] = useVersionsLoader(
gitProvider,
path
);
- const [lang, langLoading, langError] = useLanguageLoader(path);
-
- const loading = langLoading || (!commits && commitsLoading);
- const error = langError || commitsError;
if (error) {
return ;
}
- if (loading) {
+ if (!versions && loading) {
return ;
}
- if (!commits.length) {
+ if (!versions.length) {
return ;
}
- return ;
+ const commits = versions.map(v => v.commit);
+ const slideLines = versions.map(v => v.lines);
+
+ return (
+
+ );
}
-function useCommitsLoader(gitProvider, path) {
+function useVersionsLoader(gitProvider) {
const [state, setState] = useState({
data: null,
loading: true,
@@ -69,7 +65,7 @@ function useCommitsLoader(gitProvider, path) {
useEffect(() => {
gitProvider
- .getCommits(path, state.last)
+ .getVersions(state.last)
.then(data => {
setState(old => ({
data,
@@ -80,12 +76,13 @@ function useCommitsLoader(gitProvider, path) {
}));
})
.catch(error => {
- setState({
+ setState(old => ({
+ ...old,
loading: false,
- error
- });
+ error: error.message || error
+ }));
});
- }, [path, state.last]);
+ }, [state.last]);
return [state.data, state.loading, state.error, loadMore];
}
diff --git a/src/git-providers/bitbucket-commit-fetcher.js b/src/git-providers/bitbucket-commit-fetcher.js
new file mode 100644
index 0000000..1077231
--- /dev/null
+++ b/src/git-providers/bitbucket-commit-fetcher.js
@@ -0,0 +1,74 @@
+const cache = {};
+
+async function getCommits({ repo, sha, path, last, token }) {
+ if (!cache[path]) {
+ let fields =
+ "values.path,values.commit.date,values.commit.message,values.commit.hash,values.commit.author.*,values.commit.links.html, values.commit.author.user.nickname, values.commit.author.user.links.avatar.href, values.commit.links.html.href";
+ // fields = "*.*.*.*.*";
+ const commitsResponse = await fetch(
+ `https://api.bitbucket.org/2.0/repositories/${repo}/filehistory/${sha}/${path}?fields=${fields}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ if (!commitsResponse.ok) {
+ throw {
+ status: commitsResponse.status === 403 ? 404 : commitsResponse.status,
+ body: commitsJson
+ };
+ }
+
+ const commitsJson = await commitsResponse.json();
+
+ cache[path] = commitsJson.values.map(({ commit }) => ({
+ sha: commit.hash,
+ date: new Date(commit.date),
+ author: {
+ login: commit.author.user
+ ? commit.author.user.nickname
+ : commit.author.raw,
+ avatar: commit.author.user && commit.author.user.links.avatar.href
+ },
+ commitUrl: commit.links.html.href,
+ message: commit.message
+ }));
+ }
+
+ const commits = cache[path].slice(0, last);
+
+ await Promise.all(
+ commits.map(async commit => {
+ if (!commit.content) {
+ const info = await getContent(repo, commit.sha, path, token);
+ commit.content = info.content;
+ }
+ })
+ );
+
+ return commits;
+}
+
+async function getContent(repo, sha, path, token) {
+ const contentResponse = await fetch(
+ `https://api.bitbucket.org/2.0/repositories/${repo}/src/${sha}/${path}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ if (contentResponse.status === 404) {
+ return { content: "" };
+ }
+
+ if (!contentResponse.ok) {
+ throw {
+ status: contentResponse.status,
+ body: await contentResponse.json()
+ };
+ }
+
+ const content = await contentResponse.text();
+
+ return { content };
+}
+
+export default {
+ getCommits
+};
diff --git a/src/git-providers/bitbucket-provider.js b/src/git-providers/bitbucket-provider.js
index b7f253c..c913850 100644
--- a/src/git-providers/bitbucket-provider.js
+++ b/src/git-providers/bitbucket-provider.js
@@ -1,50 +1,24 @@
import netlify from "netlify-auth-providers";
-import { Base64 } from "js-base64";
import React from "react";
-const TOKEN_KEY = "bitbucket-token";
-function getHeaders() {
- const token = window.localStorage.getItem(TOKEN_KEY);
- return token ? { Authorization: `Bearer ${token}` } : {};
-}
+import versioner from "./versioner";
+import { SOURCE } from "./sources";
+
+const TOKEN_KEY = "bitbucket-token";
function isLoggedIn() {
return !!window.localStorage.getItem(TOKEN_KEY);
}
-async function getContent(repo, sha, path) {
- const contentResponse = await fetch(
- `https://api.bitbucket.org/2.0/repositories/${repo}/src/${sha}/${path}`,
- { headers: getHeaders() }
+function getUrlParams() {
+ const [, owner, reponame, , sha, ...paths] = window.location.pathname.split(
+ "/"
);
- if (contentResponse.status === 404) {
- return { content: "" };
- }
-
- if (!contentResponse.ok) {
- throw contentResponse;
+ if (!sha) {
+ return [];
}
- const content = await contentResponse.text();
- // const content = Base64.decode(contentJson.content);
- return { content };
-}
-
-function getUrlParams() {
- const [
- ,
- owner,
- reponame,
- action,
- sha,
- ...paths
- ] = window.location.pathname.split("/");
-
- // if (action !== "commits" && action !== "blob") {
- // return [];
- // }
-
return [owner + "/" + reponame, sha, paths.join("/")];
}
@@ -58,52 +32,6 @@ function showLanding() {
return !repo;
}
-const cache = {};
-
-async function getCommits(path, last) {
- const [repo, sha] = getUrlParams();
-
- if (!cache[path]) {
- let fields =
- "values.path,values.commit.date,values.commit.message,values.commit.hash,values.commit.author.*,values.commit.links.html, values.commit.author.user.nickname, values.commit.author.user.links.avatar.href, values.commit.links.html.href";
- // fields = "*.*.*.*.*";
- const commitsResponse = await fetch(
- `https://api.bitbucket.org/2.0/repositories/${repo}/filehistory/${sha}/${path}?fields=${fields}`,
- { headers: getHeaders() }
- );
- if (!commitsResponse.ok) {
- throw commitsResponse;
- }
- const commitsJson = await commitsResponse.json();
-
- cache[path] = commitsJson.values.map(({ commit }) => ({
- sha: commit.hash,
- date: new Date(commit.date),
- author: {
- login: commit.author.user
- ? commit.author.user.nickname
- : commit.author.raw,
- avatar: commit.author.user && commit.author.user.links.avatar.href
- },
- commitUrl: commit.links.html.href,
- message: commit.message
- }));
- }
-
- const commits = cache[path].slice(0, last);
-
- await Promise.all(
- commits.map(async commit => {
- if (!commit.content) {
- const info = await getContent(repo, commit.sha, path);
- commit.content = info.content;
- }
- })
- );
-
- return commits;
-}
-
function logIn() {
// return new Promise((resolve, reject) => {
var authenticator = new netlify({
@@ -131,10 +59,21 @@ function LogInButton() {
);
}
+function getParams() {
+ const [repo, sha, path] = getUrlParams();
+ const token = window.localStorage.getItem(TOKEN_KEY);
+ return { repo, sha, path, token };
+}
+
+async function getVersions(last) {
+ const params = { ...getParams(), last };
+ return await versioner.getVersions(SOURCE.BITBUCKET, params);
+}
+
export default {
showLanding,
getPath,
- getCommits,
+ getVersions,
logIn,
isLoggedIn,
LogInButton
diff --git a/src/git-providers/cli-commit-fetcher.js b/src/git-providers/cli-commit-fetcher.js
new file mode 100644
index 0000000..78e971f
--- /dev/null
+++ b/src/git-providers/cli-commit-fetcher.js
@@ -0,0 +1,14 @@
+async function getCommits({ path, last }) {
+ // TODO cache
+ const response = await fetch(
+ `/api/commits?path=${encodeURIComponent(path)}&last=${last}`
+ );
+ const commits = await response.json();
+ commits.forEach(c => (c.date = new Date(c.date)));
+
+ return commits;
+}
+
+export default {
+ getCommits
+};
diff --git a/src/git-providers/cli-provider.js b/src/git-providers/cli-provider.js
index db666ab..da93613 100644
--- a/src/git-providers/cli-provider.js
+++ b/src/git-providers/cli-provider.js
@@ -1,3 +1,6 @@
+import versioner from "./versioner";
+import { SOURCE } from "./sources";
+
function getPath() {
return new URLSearchParams(window.location.search).get("path");
}
@@ -6,19 +9,13 @@ function showLanding() {
return false;
}
-async function getCommits(path, last) {
- // TODO cache
- const response = await fetch(
- `/api/commits?path=${encodeURIComponent(path)}&last=${last}`
- );
- const commits = await response.json();
- commits.forEach(c => (c.date = new Date(c.date)));
-
- return commits;
+async function getVersions(last) {
+ const params = { path: getPath(), last };
+ return await versioner.getVersions(SOURCE.CLI, params);
}
export default {
showLanding,
- getPath,
- getCommits
+ getVersions,
+ getPath
};
diff --git a/src/differ.js b/src/git-providers/differ.js
similarity index 96%
rename from src/differ.js
rename to src/git-providers/differ.js
index 7ab5d19..0306d27 100644
--- a/src/differ.js
+++ b/src/git-providers/differ.js
@@ -1,9 +1,9 @@
-import * as diff from "diff";
+import { diffLines } from "diff";
import tokenize from "./tokenizer";
const newlineRe = /\r\n|\r|\n/;
function myDiff(oldCode, newCode) {
- const changes = diff.diffLines(oldCode || "", newCode);
+ const changes = diffLines(oldCode || "", newCode);
let oldIndex = -1;
return changes.map(({ value, count, removed, added }) => {
diff --git a/src/git-providers/github-commit-fetcher.js b/src/git-providers/github-commit-fetcher.js
new file mode 100644
index 0000000..fbd990a
--- /dev/null
+++ b/src/git-providers/github-commit-fetcher.js
@@ -0,0 +1,75 @@
+import { Base64 } from "js-base64";
+
+const cache = {};
+
+async function getCommits({ repo, sha, path, token, last }) {
+ if (!cache[path]) {
+ const commitsResponse = await fetch(
+ `https://api.github.com/repos/${repo}/commits?sha=${sha}&path=${path}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ if (!commitsResponse.ok) {
+ throw {
+ status: commitsResponse.status,
+ body: commitsJson
+ };
+ }
+
+ const commitsJson = await commitsResponse.json();
+
+ cache[path] = commitsJson.map(commit => ({
+ sha: commit.sha,
+ date: new Date(commit.commit.author.date),
+ author: {
+ login: commit.author ? commit.author.login : commit.commit.author.name,
+ avatar: commit.author
+ ? commit.author.avatar_url
+ : "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
+ },
+ commitUrl: commit.html_url,
+ message: commit.commit.message
+ }));
+ }
+
+ const commits = cache[path].slice(0, last);
+
+ await Promise.all(
+ commits.map(async commit => {
+ if (!commit.content) {
+ const info = await getContent(repo, commit.sha, path, token);
+ commit.content = info.content;
+ commit.fileUrl = info.url;
+ }
+ })
+ );
+
+ return commits;
+}
+
+async function getContent(repo, sha, path, token) {
+ const contentResponse = await fetch(
+ `https://api.github.com/repos/${repo}/contents${path}?ref=${sha}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ if (contentResponse.status === 404) {
+ return { content: "" };
+ }
+
+ const contentJson = await contentResponse.json();
+
+ if (!contentResponse.ok) {
+ throw {
+ status: contentResponse.status,
+ body: contentJson
+ };
+ }
+
+ const content = Base64.decode(contentJson.content);
+ return { content, url: contentJson.html_url };
+}
+
+export default {
+ getCommits
+};
diff --git a/src/git-providers/github-provider.js b/src/git-providers/github-provider.js
index 8471404..8055b15 100644
--- a/src/git-providers/github-provider.js
+++ b/src/git-providers/github-provider.js
@@ -1,34 +1,14 @@
import netlify from "netlify-auth-providers";
import React from "react";
-import { Base64 } from "js-base64";
-const TOKEN_KEY = "github-token";
+import versioner from "./versioner";
+import { SOURCE } from "./sources";
-function getHeaders() {
- const token = window.localStorage.getItem(TOKEN_KEY);
- return token ? { Authorization: `bearer ${token}` } : {};
-}
+const TOKEN_KEY = "github-token";
function isLoggedIn() {
return !!window.localStorage.getItem(TOKEN_KEY);
}
-async function getContent(repo, sha, path) {
- const contentResponse = await fetch(
- `https://api.github.com/repos/${repo}/contents${path}?ref=${sha}`,
- { headers: getHeaders() }
- );
-
- if (contentResponse.status === 404) {
- return { content: "" };
- }
- if (!contentResponse.ok) {
- throw contentResponse;
- }
- const contentJson = await contentResponse.json();
- const content = Base64.decode(contentJson.content);
- return { content, url: contentJson.html_url };
-}
-
function getUrlParams() {
const [
,
@@ -56,50 +36,6 @@ function showLanding() {
return !repo;
}
-const cache = {};
-
-async function getCommits(path, last) {
- const [repo, sha] = getUrlParams();
-
- if (!cache[path]) {
- const commitsResponse = await fetch(
- `https://api.github.com/repos/${repo}/commits?sha=${sha}&path=${path}`,
- { headers: getHeaders() }
- );
- if (!commitsResponse.ok) {
- throw commitsResponse;
- }
- const commitsJson = await commitsResponse.json();
-
- cache[path] = commitsJson.map(commit => ({
- sha: commit.sha,
- date: new Date(commit.commit.author.date),
- author: {
- login: commit.author ? commit.author.login : commit.commit.author.name,
- avatar: commit.author
- ? commit.author.avatar_url
- : "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
- },
- commitUrl: commit.html_url,
- message: commit.commit.message
- }));
- }
-
- const commits = cache[path].slice(0, last);
-
- await Promise.all(
- commits.map(async commit => {
- if (!commit.content) {
- const info = await getContent(repo, commit.sha, path);
- commit.content = info.content;
- commit.fileUrl = info.url;
- }
- })
- );
-
- return commits;
-}
-
function logIn() {
// return new Promise((resolve, reject) => {
var authenticator = new netlify({
@@ -143,10 +79,22 @@ function LogInButton() {
);
}
+function getParams() {
+ const [repo, sha, path] = getUrlParams();
+ const token = window.localStorage.getItem(TOKEN_KEY);
+ return { repo, sha, path, token };
+}
+
+async function getVersions(last) {
+ const params = { ...getParams(), last };
+ return await versioner.getVersions(SOURCE.GITHUB, params);
+}
+
export default {
showLanding,
getPath,
- getCommits,
+ getParams,
+ getVersions,
logIn,
isLoggedIn,
LogInButton
diff --git a/src/git-providers/gitlab-commit-fetcher.js b/src/git-providers/gitlab-commit-fetcher.js
new file mode 100644
index 0000000..fbecb85
--- /dev/null
+++ b/src/git-providers/gitlab-commit-fetcher.js
@@ -0,0 +1,75 @@
+import { Base64 } from "js-base64";
+
+const cache = {};
+
+async function getCommits({ repo, sha, path, token, last }) {
+ if (!cache[path]) {
+ const commitsResponse = await fetch(
+ `https://gitlab.com/api/v4/projects/${encodeURIComponent(
+ repo
+ )}/repository/commits?path=${encodeURIComponent(path)}&ref_name=${sha}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ const commitsJson = await commitsResponse.json();
+
+ if (!commitsResponse.ok) {
+ throw {
+ status: commitsResponse.status,
+ body: commitsJson
+ };
+ }
+
+ cache[path] = commitsJson.map(commit => ({
+ sha: commit.id,
+ date: new Date(commit.authored_date),
+ author: {
+ login: commit.author_name
+ },
+ // commitUrl: commit.html_url,
+ message: commit.title
+ }));
+ }
+
+ const commits = cache[path].slice(0, last);
+
+ await Promise.all(
+ commits.map(async commit => {
+ if (!commit.content) {
+ const info = await getContent(repo, commit.sha, path, token);
+ commit.content = info.content;
+ }
+ })
+ );
+
+ return commits;
+}
+
+async function getContent(repo, sha, path, token) {
+ const contentResponse = await fetch(
+ `https://gitlab.com/api/v4/projects/${encodeURIComponent(
+ repo
+ )}/repository/files/${encodeURIComponent(path)}?ref=${sha}`,
+ { headers: token ? { Authorization: `bearer ${token}` } : {} }
+ );
+
+ if (contentResponse.status === 404) {
+ return { content: "" };
+ }
+
+ const contentJson = await contentResponse.json();
+
+ if (!contentResponse.ok) {
+ throw {
+ status: contentResponse.status,
+ body: contentJson
+ };
+ }
+
+ const content = Base64.decode(contentJson.content);
+ return { content };
+}
+
+export default {
+ getCommits
+};
diff --git a/src/git-providers/gitlab-provider.js b/src/git-providers/gitlab-provider.js
index 088183c..257a6c0 100644
--- a/src/git-providers/gitlab-provider.js
+++ b/src/git-providers/gitlab-provider.js
@@ -1,36 +1,15 @@
import netlify from "netlify-auth-providers";
-import { Base64 } from "js-base64";
import React from "react";
-const TOKEN_KEY = "gitlab-token";
-function getHeaders() {
- const token = window.localStorage.getItem(TOKEN_KEY);
- return token ? { Authorization: `Bearer ${token}` } : {};
-}
+import versioner from "./versioner";
+import { SOURCE } from "./sources";
+
+const TOKEN_KEY = "gitlab-token";
function isLoggedIn() {
return !!window.localStorage.getItem(TOKEN_KEY);
}
-async function getContent(repo, sha, path) {
- const contentResponse = await fetch(
- `https://gitlab.com/api/v4/projects/${encodeURIComponent(
- repo
- )}/repository/files/${encodeURIComponent(path)}?ref=${sha}`,
- { headers: getHeaders() }
- );
-
- if (contentResponse.status === 404) {
- return { content: "" };
- }
- if (!contentResponse.ok) {
- throw contentResponse;
- }
- const contentJson = await contentResponse.json();
- const content = Base64.decode(contentJson.content);
- return { content };
-}
-
function getUrlParams() {
const [
,
@@ -58,48 +37,6 @@ function showLanding() {
return !repo;
}
-const cache = {};
-
-async function getCommits(path, last) {
- const [repo, sha] = getUrlParams();
-
- if (!cache[path]) {
- const commitsResponse = await fetch(
- `https://gitlab.com/api/v4/projects/${encodeURIComponent(
- repo
- )}/repository/commits?path=${encodeURIComponent(path)}&ref_name=${sha}`,
- { headers: getHeaders() }
- );
- if (!commitsResponse.ok) {
- throw commitsResponse;
- }
- const commitsJson = await commitsResponse.json();
-
- cache[path] = commitsJson.map(commit => ({
- sha: commit.id,
- date: new Date(commit.authored_date),
- author: {
- login: commit.author_name
- },
- // commitUrl: commit.html_url,
- message: commit.title
- }));
- }
-
- const commits = cache[path].slice(0, last);
-
- await Promise.all(
- commits.map(async commit => {
- if (!commit.content) {
- const info = await getContent(repo, commit.sha, path);
- commit.content = info.content;
- }
- })
- );
-
- return commits;
-}
-
function logIn() {
// return new Promise((resolve, reject) => {
var authenticator = new netlify({
@@ -130,10 +67,21 @@ function LogInButton() {
);
}
+function getParams() {
+ const [repo, sha, path] = getUrlParams();
+ const token = window.localStorage.getItem(TOKEN_KEY);
+ return { repo, sha, path, token };
+}
+
+async function getVersions(last) {
+ const params = { ...getParams(), last };
+ return await versioner.getVersions(SOURCE.GITLAB, params);
+}
+
export default {
showLanding,
getPath,
- getCommits,
+ getVersions,
logIn,
isLoggedIn,
LogInButton
diff --git a/src/language-detector.js b/src/git-providers/language-detector.js
similarity index 100%
rename from src/language-detector.js
rename to src/git-providers/language-detector.js
diff --git a/src/language-detector.test.js b/src/git-providers/language-detector.test.js
similarity index 100%
rename from src/language-detector.test.js
rename to src/git-providers/language-detector.test.js
diff --git a/src/git-providers/providers.js b/src/git-providers/providers.js
index 02e5a4b..60a32d3 100644
--- a/src/git-providers/providers.js
+++ b/src/git-providers/providers.js
@@ -1,23 +1,22 @@
-import cliProvider from "./cli-provider";
-import githubProvider from "./github-provider";
-import vscodeProvider from "./vscode-provider";
-import gitlabProvider from "./gitlab-provider";
-import bitbucketProvider from "./bitbucket-provider";
+const { SOURCE, getSource } = require("./sources");
-export default function getGitProvider() {
- switch (process.env.REACT_APP_GIT_PROVIDER) {
- case "cli":
- return cliProvider;
- case "vscode":
- return vscodeProvider;
- default: {
- const [cloud] = window.location.host.split(".");
- if (cloud === "gitlab") {
- return gitlabProvider;
- } else if (cloud === "bitbucket") {
- return bitbucketProvider;
- }
- return githubProvider;
- }
- }
+let providers;
+if (process.env.REACT_APP_GIT_PROVIDER === SOURCE.VSCODE) {
+ // We can't use web workers on vscode webview
+ providers = {
+ [SOURCE.VSCODE]: require("./vscode-provider").default
+ };
+} else {
+ providers = {
+ [SOURCE.CLI]: require("./cli-provider").default,
+ [SOURCE.GITLAB]: require("./gitlab-provider").default,
+ [SOURCE.GITHUB]: require("./github-provider").default,
+ [SOURCE.BITBUCKET]: require("./bitbucket-provider").default
+ };
+}
+
+export default function getGitProvider(source) {
+ source = source || getSource();
+ const provider = providers[source];
+ return provider;
}
diff --git a/src/git-providers/sources.js b/src/git-providers/sources.js
new file mode 100644
index 0000000..76fa372
--- /dev/null
+++ b/src/git-providers/sources.js
@@ -0,0 +1,19 @@
+export const SOURCE = {
+ GITHUB: "github",
+ GITLAB: "gitlab",
+ BITBUCKET: "bitbucket",
+ CLI: "cli",
+ VSCODE: "vscode"
+};
+
+export function getSource() {
+ if (process.env.REACT_APP_GIT_PROVIDER)
+ return process.env.REACT_APP_GIT_PROVIDER;
+
+ const [cloud] = window.location.host.split(".");
+ if ([SOURCE.GITLAB, SOURCE.GITHUB, SOURCE.BITBUCKET].includes(cloud)) {
+ return cloud;
+ }
+ const source = new URLSearchParams(window.location.search).get("source");
+ return source || SOURCE.GITHUB;
+}
diff --git a/src/tokenizer.js b/src/git-providers/tokenizer.js
similarity index 90%
rename from src/tokenizer.js
rename to src/git-providers/tokenizer.js
index 681445b..129f619 100644
--- a/src/tokenizer.js
+++ b/src/git-providers/tokenizer.js
@@ -1,4 +1,6 @@
-import Prism from "prismjs";
+// https://github.com/PrismJS/prism/issues/1303#issuecomment-375353987
+global.Prism = { disableWorkerMessageHandler: true };
+const Prism = require("prismjs");
const newlineRe = /\r\n|\r|\n/;
diff --git a/src/git-providers/versioner.js b/src/git-providers/versioner.js
new file mode 100644
index 0000000..ac7c804
--- /dev/null
+++ b/src/git-providers/versioner.js
@@ -0,0 +1,5 @@
+/* eslint-disable import/no-webpack-loader-syntax */
+import worker from "workerize-loader!./versioner.worker";
+let versioner = worker();
+
+export default versioner;
diff --git a/src/git-providers/versioner.worker.js b/src/git-providers/versioner.worker.js
new file mode 100644
index 0000000..3f95383
--- /dev/null
+++ b/src/git-providers/versioner.worker.js
@@ -0,0 +1,29 @@
+import { getLanguage, loadLanguage } from "./language-detector";
+import { getSlides } from "./differ";
+
+import github from "./github-commit-fetcher";
+import gitlab from "./gitlab-commit-fetcher";
+import bitbucket from "./bitbucket-commit-fetcher";
+import cli from "./cli-commit-fetcher";
+import { SOURCE } from "./sources";
+
+const fetchers = {
+ [SOURCE.GITHUB]: github.getCommits,
+ [SOURCE.GITLAB]: gitlab.getCommits,
+ [SOURCE.BITBUCKET]: bitbucket.getCommits,
+ [SOURCE.CLI]: cli.getCommits
+};
+
+export async function getVersions(source, params) {
+ const { path } = params;
+ const lang = getLanguage(path);
+ const langPromise = loadLanguage(lang);
+
+ const getCommits = fetchers[source];
+ const commits = await getCommits(params);
+ await langPromise;
+
+ const codes = commits.map(commit => commit.content);
+ const slides = getSlides(codes, lang);
+ return commits.map((commit, i) => ({ commit, lines: slides[i] }));
+}
diff --git a/src/git-providers/vscode-provider.js b/src/git-providers/vscode-provider.js
index bdf139e..3ed205c 100644
--- a/src/git-providers/vscode-provider.js
+++ b/src/git-providers/vscode-provider.js
@@ -1,3 +1,6 @@
+import { getLanguage, loadLanguage } from "./language-detector";
+import { getSlides } from "./differ";
+
const vscode = window.vscode;
function getPath() {
@@ -30,8 +33,21 @@ function getCommits(path, last) {
});
}
+async function getVersions(last) {
+ const path = getPath();
+ const lang = getLanguage(path);
+ const langPromise = loadLanguage(lang);
+
+ const commits = await getCommits(path, last);
+ await langPromise;
+
+ const codes = commits.map(commit => commit.content);
+ const slides = getSlides(codes, lang);
+ return commits.map((commit, i) => ({ commit, lines: slides[i] }));
+}
+
export default {
showLanding,
getPath,
- getCommits
+ getVersions
};
diff --git a/src/history.js b/src/history.js
index 20c521d..7fe82c3 100644
--- a/src/history.js
+++ b/src/history.js
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
-import { getSlides } from "./differ";
import useSpring from "react-use/lib/useSpring";
import Swipeable from "react-swipeable";
import Slide from "./slide";
@@ -43,7 +42,11 @@ function CommitInfo({ commit, move, onClick }) {
{isActive && commit.commitUrl ? (
-
+
on {commit.date.toDateString()}
) : (
@@ -96,10 +99,7 @@ function CommitList({ commits, currentIndex, selectCommit }) {
);
}
-export default function History({ commits, language, loadMore }) {
- const codes = commits.map(commit => commit.content);
- const slideLines = getSlides(codes, language);
-
+export default function History({ commits, slideLines, loadMore }) {
return (
);
@@ -135,7 +135,11 @@ function Slides({ commits, slideLines, loadMore }) {
currentIndex={current}
selectCommit={index => setClampedTarget(index)}
/>
-
+
diff --git a/src/landing.js b/src/landing.js
index 3822a8c..57555ff 100644
--- a/src/landing.js
+++ b/src/landing.js
@@ -69,6 +69,7 @@ export default function Landing() {
{
+ ref.current.scrollTop = top;
+ }, [top]);
+
+ return (
+ setTop(e.target.scrollTop)}
+ children={children}
+ />
+ );
+}
+
+function useHeight(ref) {
+ let [height, setHeight] = React.useState(null);
+
+ function handleResize() {
+ setHeight(ref.current.clientHeight);
+ }
+
+ React.useEffect(() => {
+ handleResize();
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [ref.current]);
+
+ return height;
+}
diff --git a/src/slide.js b/src/slide.js
index 7cd698e..0c9f107 100644
--- a/src/slide.js
+++ b/src/slide.js
@@ -1,6 +1,7 @@
import React from "react";
import animation from "./animation";
import theme from "./nightOwl";
+import Scroller from "./scroller";
const themeStylesByType = Object.create(null);
theme.styles.forEach(({ types, style }) => {
@@ -12,9 +13,17 @@ theme.styles.forEach(({ types, style }) => {
});
});
-function Line({ line, style }) {
+function getLineHeight(line, i, { styles }) {
+ return styles[i].height != null ? styles[i].height : 15;
+}
+
+function getLine(line, i, { styles }) {
+ const style = styles[i];
return (
-
+
{line.tokens.map((token, i) => {
const style = themeStylesByType[token.type] || {};
return (
@@ -27,17 +36,18 @@ function Line({ line, style }) {
);
}
-export default function Slide({ time, lines }) {
- const styles = animation((time + 1) / 2, lines);
+function Slide({ lines, styles }) {
+ const [top, setTop] = React.useState(0);
return (
- {lines.map((line, i) => (
-
- ))}
+
);
}
+
+export default function SlideWrapper({ time, lines }) {
+ const styles = animation((time + 1) / 2, lines);
+
+ return
;
+}
diff --git a/src/todo.md b/src/todo.md
new file mode 100644
index 0000000..4697742
--- /dev/null
+++ b/src/todo.md
@@ -0,0 +1,3 @@
+- make diffing incremental
+- cache all the react elements from the lines + styles
+- only set scrollTop if `top` is different from the last value from the event (and clean the last value from the event after that) https://codesandbox.io/s/r089yvk82m
diff --git a/src/use-virtual-children.js b/src/use-virtual-children.js
new file mode 100644
index 0000000..5fc565c
--- /dev/null
+++ b/src/use-virtual-children.js
@@ -0,0 +1,43 @@
+import React from "react";
+
+export default function useChildren({
+ items,
+ getRow,
+ getRowHeight,
+ height,
+ top,
+ data
+}) {
+ const children = [];
+
+ const extraRender = 1500;
+
+ const topT = top - extraRender;
+ const bottomT = top + height + extraRender;
+ let h = 0;
+
+ let topPlaceHolderH = 0;
+ let bottomPlaceholderH = 0;
+
+ // This is the bottleneck
+ items.forEach((item, i) => {
+ const itemH = getRowHeight(item, i, data);
+ const nextH = h + itemH;
+ const isOverTop = nextH < topT;
+ const isUnderBottom = h > bottomT;
+
+ if (isOverTop) {
+ topPlaceHolderH += itemH;
+ } else if (isUnderBottom) {
+ bottomPlaceholderH += itemH;
+ } else {
+ children.push(getRow(item, i, data));
+ }
+
+ h = nextH;
+ });
+
+ children.unshift(
);
+ children.push(
);
+ return children;
+}
diff --git a/yarn.lock b/yarn.lock
index a81aebc..451f834 100755
--- a/yarn.lock
+++ b/yarn.lock
@@ -865,6 +865,15 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
+"@craco/craco@^3.5.0":
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-3.5.0.tgz#698e9f808e4fc5723345a38d8b505a465d885234"
+ integrity sha512-peQtKgJawPGq5D8qqW2oxkSzCp1wwWL7H8G98Run3UxF5fVJjJLIcnxoRKvXFmrMQYuKBOOsO54ApEeJdTzC0Q==
+ dependencies:
+ cross-spawn "6.0.5"
+ lodash.mergewith "4.6.1"
+ webpack-merge "4.1.4"
+
"@csstools/convert-colors@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7"
@@ -5986,6 +5995,11 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
+lodash.mergewith@4.6.1:
+ version "4.6.1"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
+ integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
+
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@@ -9977,6 +9991,13 @@ webpack-manifest-plugin@2.0.4:
lodash ">=3.5 <5"
tapable "^1.0.0"
+webpack-merge@4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.4.tgz#0fde38eabf2d5fd85251c24a5a8c48f8a3f4eb7b"
+ integrity sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==
+ dependencies:
+ lodash "^4.17.5"
+
webpack-sources@^1.1.0, webpack-sources@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
@@ -10226,6 +10247,13 @@ worker-farm@^1.5.2:
dependencies:
errno "~0.1.7"
+workerize-loader@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/workerize-loader/-/workerize-loader-1.0.4.tgz#5e7d1b7775e842399be50a761e0c4dd902ff42f1"
+ integrity sha512-HMTr/zpuZhm8dbhcK52cMYmn57uf7IJeMZJil+5lL/vC5+AO9wzxZ0FISkGVj78No7HcpaINwAWHGCYx3dnsTw==
+ dependencies:
+ loader-utils "^1.1.0"
+
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"