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..7b85fec 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "repository": "pomber/git-history", "private": true, "dependencies": { + "@craco/craco": "^3.4.1", "diff": "^4.0.1", "js-base64": "^2.5.1", "netlify-auth-providers": "^1.0.0-alpha5", @@ -12,10 +13,11 @@ "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", + "start": "craco start", "build": "react-scripts 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", 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..39d0b29 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 + })); }); - }, [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..1042340 --- /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: contentJson + }; + } + + 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 e561938..c913850 100644 --- a/src/git-providers/bitbucket-provider.js +++ b/src/git-providers/bitbucket-provider.js @@ -1,35 +1,15 @@ import netlify from "netlify-auth-providers"; 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() } - ); - - if (contentResponse.status === 404) { - return { content: "" }; - } - - if (!contentResponse.ok) { - throw contentResponse; - } - - const content = await contentResponse.text(); - - return { content }; -} - function getUrlParams() { const [, owner, reponame, , sha, ...paths] = window.location.pathname.split( "/" @@ -52,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({ @@ -125,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..f92efd9 --- /dev/null +++ b/src/git-providers/github-commit-fetcher.js @@ -0,0 +1,74 @@ +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: "" }; + } + + if (!contentResponse.ok) { + throw { + status: contentResponse.status, + body: contentJson + }; + } + + const contentJson = await contentResponse.json(); + 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..d2be448 --- /dev/null +++ b/src/git-providers/gitlab-commit-fetcher.js @@ -0,0 +1,74 @@ +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: "" }; + } + + if (!contentResponse.ok) { + throw { + status: contentResponse.status, + body: contentJson + }; + } + + const contentJson = await contentResponse.json(); + 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..0035fcb 100644 --- a/src/git-providers/providers.js +++ b/src/git-providers/providers.js @@ -3,21 +3,18 @@ import githubProvider from "./github-provider"; import vscodeProvider from "./vscode-provider"; import gitlabProvider from "./gitlab-provider"; import bitbucketProvider from "./bitbucket-provider"; +import { SOURCE, getSource } from "./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; - } - } +const providers = { + [SOURCE.CLI]: cliProvider, + [SOURCE.VSCODE]: vscodeProvider, + [SOURCE.GITLAB]: gitlabProvider, + [SOURCE.GITHUB]: githubProvider, + [SOURCE.BITBUCKET]: bitbucketProvider +}; + +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..81aa0b9 --- /dev/null +++ b/src/git-providers/sources.js @@ -0,0 +1,18 @@ +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; + } + return SOURCE.BITBUCKET; +} 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 cf2eda9..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 ( ); 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() { setTop(e.target.scrollTop)} diff --git a/src/todo.md b/src/todo.md index f166701..2c65d5a 100644 --- a/src/todo.md +++ b/src/todo.md @@ -1,4 +1,4 @@ - move diffing to web worker - 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) +- 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/yarn.lock b/yarn.lock index 324203d..307b674 100755 --- a/yarn.lock +++ b/yarn.lock @@ -865,6 +865,15 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" +"@craco/craco@^3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@craco/craco/-/craco-3.4.1.tgz#a65e29d7cb661e09e68269ba09ade39d174ec359" + integrity sha512-PVuDx5iLzoYeFrjNUVbblQCGzFyw/DoBBHFI4+JlSCvgEkp07jbBm5fe+z/MqzYJUQm1bFbxe6qbQWI5LFl6XA== + 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" @@ -1141,11 +1150,6 @@ acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz#81730c0815f3f3b34d8efa95cb7430965f4d887a" integrity sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg== -add-px-to-style@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a" - integrity sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo= - address@1.0.3, address@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" @@ -3095,15 +3099,6 @@ dom-converter@~0.2: dependencies: utila "~0.4" -dom-css@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/dom-css/-/dom-css-2.1.0.tgz#fdbc2d5a015d0a3e1872e11472bbd0e7b9e6a202" - integrity sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI= - dependencies: - add-px-to-style "1.0.0" - prefix-style "2.0.1" - to-camel-case "1.0.0" - dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" @@ -6000,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" @@ -6040,7 +6040,7 @@ loglevel@^1.4.1: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7797,11 +7797,6 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.5, postcss@^7.0.7: source-map "^0.6.1" supports-color "^5.5.0" -prefix-style@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/prefix-style/-/prefix-style-2.0.1.tgz#66bba9a870cfda308a5dc20e85e9120932c95a06" - integrity sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY= - prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -7890,15 +7885,6 @@ prompts@^0.1.9: kleur "^2.0.1" sisteransi "^0.1.1" -prop-types@^15.5.10: - version "15.7.2" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" - integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.8.1" - prop-types@^15.5.8, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -8014,13 +8000,6 @@ raf@3.4.0: dependencies: performance-now "^2.1.0" -raf@^3.1.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" - integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== - dependencies: - performance-now "^2.1.0" - randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" @@ -8081,15 +8060,6 @@ react-app-polyfill@^0.2.0: raf "3.4.0" whatwg-fetch "3.0.0" -react-custom-scrollbars@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db" - integrity sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts= - dependencies: - dom-css "^2.0.0" - prop-types "^15.5.10" - raf "^3.1.0" - react-dev-utils@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-7.0.1.tgz#c53e759a22668ee2c0d146c24ce4bdec2b41e3c8" @@ -8135,11 +8105,6 @@ react-error-overlay@^5.1.2: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-5.1.2.tgz#888957b884d4b25b083a82ad550f7aad96585394" integrity sha512-7kEBKwU9R8fKnZJBRa5RSIfay4KJwnYvKB6gODGicUmDSAhQJ7Tdnll5S0RLtYrzRfMVXlqYw61rzrSpP4ThLQ== -react-is@^16.8.1: - version "16.8.3" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.3.tgz#4ad8b029c2a718fc0cfc746c8d4e1b7221e5387d" - integrity sha512-Y4rC1ZJmsxxkkPuMLwvKvlL1Zfpbcu+Bf4ZigkHup3v9EfdYhAlWAaVyA19olXq2o2mGn0w+dFKvk3pVVlYcIA== - react-scripts@2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-2.1.3.tgz#6e49be279f4039fb9f330d2b3529b933b8e90945" @@ -9539,13 +9504,6 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-camel-case@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" - integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY= - dependencies: - to-space-case "^1.0.0" - to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -9556,11 +9514,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-no-case@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" - integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= - to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -9586,13 +9539,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -to-space-case@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" - integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= - dependencies: - to-no-case "^1.0.0" - topo@2.x.x: version "2.0.2" resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" @@ -10045,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" @@ -10294,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"