mirror of
https://github.com/decompme/decomp.me.git
synced 2026-02-20 13:29:14 -06:00
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"presets": ["next/babel"],
|
||||
"plugins": []
|
||||
"plugins": ["@shopify/web-worker/babel"]
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const removeImports = require("next-remove-imports")({
|
||||
//matchImports: "\\.(less|css|scss|sass|styl)$"
|
||||
})
|
||||
const nextTranslate = require("next-translate")
|
||||
const { WebWorkerPlugin } = require("@shopify/web-worker/webpack")
|
||||
|
||||
const mediaUrl = new URL(process.env.MEDIA_URL ?? "http://localhost")
|
||||
|
||||
@@ -76,6 +77,9 @@ let app = withPlausibleProxy({
|
||||
use: ["@svgr/webpack"],
|
||||
})
|
||||
|
||||
config.plugins.push(new WebWorkerPlugin())
|
||||
config.output.globalObject = "self"
|
||||
|
||||
return config
|
||||
},
|
||||
images: {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@primer/octicons-react": "^17.2.0",
|
||||
"@react-hook/resize-observer": "^1.2.2",
|
||||
"@replit/codemirror-indentation-markers": "^6.1.0",
|
||||
"@shopify/web-worker": "^5.0.1",
|
||||
"ansi-to-react": "^6.1.6",
|
||||
"classnames": "^2.3.1",
|
||||
"codemirror": "^6.0.1",
|
||||
@@ -46,7 +47,8 @@
|
||||
"swr": "^1.3.0",
|
||||
"use-debounce": "^8.0.1",
|
||||
"use-deep-compare-effect": "^1.6.1",
|
||||
"use-persisted-state": "^0.3.3"
|
||||
"use-persisted-state": "^0.3.3",
|
||||
"webpack-virtual-modules": "^0.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.17.8",
|
||||
|
||||
@@ -1,64 +1,21 @@
|
||||
import { RefObject, useEffect, useRef, useState } from "react"
|
||||
import { RefObject, useEffect, useState } from "react"
|
||||
|
||||
import { EditorState, Compartment, Extension, Facet, Text } from "@codemirror/state"
|
||||
import { EditorView, gutter, GutterMarker } from "@codemirror/view"
|
||||
import { diff } from "fast-myers-diff"
|
||||
import { Compartment, Extension, Facet } from "@codemirror/state"
|
||||
import { EditorView, gutter, GutterMarker, ViewPlugin, ViewUpdate } from "@codemirror/view"
|
||||
import { createWorkerFactory } from "@shopify/web-worker"
|
||||
|
||||
import styles from "./useCompareExtension.module.scss"
|
||||
|
||||
function compareNullableText(a: Text | null, b: Text | null): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b
|
||||
} else {
|
||||
return a.eq(b)
|
||||
}
|
||||
}
|
||||
|
||||
// State for target text to diff doc against
|
||||
const targetString = Facet.define<string, string | null>({
|
||||
combine: values => (values.length ? values[0] : null),
|
||||
})
|
||||
const targetText = Facet.define<Text, Text | null>({
|
||||
combine: values => (values.length ? values[0] : null),
|
||||
compare: compareNullableText,
|
||||
compareInput: compareNullableText,
|
||||
})
|
||||
const targetTextComputer = targetText.compute([targetString], state => {
|
||||
const s = state.facet(targetString)
|
||||
if (typeof s === "string")
|
||||
return Text.of(s.split("\n"))
|
||||
return null
|
||||
})
|
||||
|
||||
// Computed diff between doc and target
|
||||
type DiffLineMap = Record<number, GutterMarker>
|
||||
const diffLineMap = Facet.define<DiffLineMap, DiffLineMap>({
|
||||
combine: values => (values.length ? values[0] : {}),
|
||||
})
|
||||
const diffLineMapComputer = diffLineMap.compute(["doc", targetString], state => {
|
||||
const s = state.facet(targetString)
|
||||
|
||||
if (typeof s !== "string")
|
||||
return {}
|
||||
|
||||
const tokenizeSource = source => {
|
||||
return source.split("\n").map(i => i.trim())
|
||||
}
|
||||
|
||||
const diffsIterator = diff(tokenizeSource(s), tokenizeSource(state.doc.toString()))
|
||||
const diffs = Array.from(diffsIterator)
|
||||
|
||||
// Convert diff changes to a map of line numbers -> change type
|
||||
const map: DiffLineMap = {}
|
||||
|
||||
for (const [, , childStartLine, childEndLine] of diffs) {
|
||||
for (let i = childStartLine; i < childEndLine; i++) {
|
||||
map[i + 1] = marker
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
})
|
||||
|
||||
const marker = new class extends GutterMarker {
|
||||
toDOM() {
|
||||
@@ -88,34 +45,51 @@ const diffGutter = gutter({
|
||||
}
|
||||
},
|
||||
lineMarkerChange(update) {
|
||||
return update.docChanged || !compareNullableText(update.state.facet(targetText), update.startState.facet(targetText))
|
||||
return update.docChanged || (update.state.facet(diffLineMap) != update.startState.facet(diffLineMap))
|
||||
},
|
||||
initialSpacer: () => marker,
|
||||
})
|
||||
|
||||
export function useDelayedCompareExtension(viewRef: RefObject<EditorView>, compareTo: string, delayMs = 1000): Extension {
|
||||
const editTime = useRef(0)
|
||||
const timeSinceEdit = Date.now() - editTime.current
|
||||
const timeout = useRef<any>()
|
||||
const [, forceUpdate] = useState({})
|
||||
const createDiffWorker = createWorkerFactory(() => import("./useCompareExtension.worker"))
|
||||
|
||||
return [
|
||||
useCompareExtension(viewRef, timeSinceEdit > delayMs ? compareTo : undefined),
|
||||
EditorState.transactionExtender.of(tr => {
|
||||
if (tr.docChanged) {
|
||||
editTime.current = Date.now()
|
||||
const diffLineMapCompartment = new Compartment()
|
||||
const diffLineCalcPlugin = ViewPlugin.fromClass(class {
|
||||
private worker = createDiffWorker()
|
||||
|
||||
if (timeout.current)
|
||||
clearTimeout(timeout.current)
|
||||
timeout.current = setTimeout(() => {
|
||||
forceUpdate({})
|
||||
}, delayMs)
|
||||
constructor(private view: EditorView) {
|
||||
this.updateDiff()
|
||||
}
|
||||
|
||||
async update(update: ViewUpdate) {
|
||||
if (update.docChanged) {
|
||||
this.updateDiff()
|
||||
}
|
||||
}
|
||||
|
||||
async updateDiff() {
|
||||
const diff = await this.worker.calculateDiff(this.view.state.facet(targetString), this.view.state.doc.toString())
|
||||
|
||||
// Convert diff changes to a map of line numbers -> change type
|
||||
let map: DiffLineMap = {}
|
||||
|
||||
for (const [, , childStartLine, childEndLine] of diff) {
|
||||
for (let i = childStartLine; i < childEndLine; i++) {
|
||||
map[i + 1] = marker
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}),
|
||||
]
|
||||
}
|
||||
// Has our targetString been updated to a blank,
|
||||
// and thus we should be showing no diff right now, while
|
||||
// the view's been updating?
|
||||
if (typeof this.view.state.facet(targetString) !== "string") {
|
||||
map = {}
|
||||
}
|
||||
|
||||
this.view.dispatch({
|
||||
effects: diffLineMapCompartment.reconfigure(diffLineMap.of(map)),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Extension that highlights lines in the doc that differ from `compareTo`.
|
||||
export default function useCompareExtension(viewRef: RefObject<EditorView>, compareTo: string): Extension {
|
||||
@@ -131,9 +105,9 @@ export default function useCompareExtension(viewRef: RefObject<EditorView>, comp
|
||||
}, [compartment, compareTo, viewRef])
|
||||
|
||||
return [
|
||||
targetTextComputer,
|
||||
diffLineMapComputer,
|
||||
diffGutter,
|
||||
compartment.of(targetString.of(compareTo)),
|
||||
diffLineMapCompartment.of(diffLineMap.of({})),
|
||||
diffLineCalcPlugin,
|
||||
]
|
||||
}
|
||||
|
||||
14
frontend/src/lib/codemirror/useCompareExtension.worker.ts
Normal file
14
frontend/src/lib/codemirror/useCompareExtension.worker.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { diff } from "fast-myers-diff"
|
||||
|
||||
export function calculateDiff(target: string | undefined, current: string): [number, number, number, number][] {
|
||||
if (typeof target !== "string") {
|
||||
return []
|
||||
}
|
||||
|
||||
const tokenizeSource = source => {
|
||||
return source.split("\n").map(i => i.trim())
|
||||
}
|
||||
|
||||
const diffsIterator = diff(tokenizeSource(target), tokenizeSource(current))
|
||||
return Array.from(diffsIterator)
|
||||
}
|
||||
@@ -1393,6 +1393,11 @@
|
||||
"@react-hook/latest" "^1.0.2"
|
||||
"@react-hook/passive-layout-effect" "^1.2.0"
|
||||
|
||||
"@remote-ui/rpc@^1.2.5":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@remote-ui/rpc/-/rpc-1.4.1.tgz#c9a5b69819710c7e35392272c56df6e76322356f"
|
||||
integrity sha512-YLjZqAeTolxLPvH59jO1W5/A1M0uK8IDtbfnrU8LXEaUPn9kmE7w5z2Isa+Do5m+YjTCn4y5bNHBsUTTsmhO0w==
|
||||
|
||||
"@replit/codemirror-indentation-markers@^6.1.0":
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.1.0.tgz#53ca559f25609c90e2e905624bb0aa57c7b3041a"
|
||||
@@ -1440,6 +1445,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz#8be36a1f66f3265389e90b5f9c9962146758f728"
|
||||
integrity sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==
|
||||
|
||||
"@shopify/web-worker@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@shopify/web-worker/-/web-worker-5.0.1.tgz#54b377fae5522b405e004c4971b3924a7b361295"
|
||||
integrity sha512-fGNcUkzqA9h2dD/3/zBd2YEPCXePN9Mmy53alb6DgpUq8nEgEE1yxj9+PfDi7RsUM+T36d8/QXgReOZ4NvlxuA==
|
||||
dependencies:
|
||||
"@remote-ui/rpc" "^1.2.5"
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz#ee34985952ca21558ab0d952f00298ad2190c053"
|
||||
@@ -6027,6 +6039,11 @@ webpack-sources@^3.2.3:
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||
|
||||
webpack-virtual-modules@^0.4.5:
|
||||
version "0.4.6"
|
||||
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz#3e4008230731f1db078d9cb6f68baf8571182b45"
|
||||
integrity sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==
|
||||
|
||||
webpack@5:
|
||||
version "5.75.0"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.75.0.tgz#1e440468647b2505860e94c9ff3e44d5b582c152"
|
||||
|
||||
Reference in New Issue
Block a user