Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion apps/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,28 @@
<input id="wrap-lines" type="checkbox" />
Wrap
</label>
<label>
<input id="lag-radar" type="checkbox" />
Lag Radar
</label>
</div>
</div>
<div id="wrapper" class="wrapper" />
<div id="wrapper" class="wrapper"></div>
</div>
<!-- lag radar -->
<div
id="radar"
style="
display: none;
position: fixed;
bottom: 4px;
right: 4px;
width: 100px;
height: 100px;
z-index: 100;
pointer-events: none;
"
></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
138 changes: 134 additions & 4 deletions apps/demo/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
VirtualizedFileDiff,
Virtualizer,
} from '@pierre/diffs';
import { Editor } from '@pierre/diffs/editor';
import type { WorkerPoolManager } from '@pierre/diffs/worker';

import {
Expand Down Expand Up @@ -108,8 +109,18 @@ function cleanupInstances(container: HTMLElement) {
cleanupCodeView(container);
container.textContent = '';
delete container.dataset.diff;
editShortcutCallback = undefined;
}

let editShortcutCallback: (() => boolean | void) | undefined;
document.addEventListener('keydown', (event) => {
if (event.key === 'e') {
if (editShortcutCallback?.() === false) {
event.preventDefault();
}
}
});

let loadingPatch: Promise<string> | undefined;
async function loadPatchContent() {
loadingPatch =
Expand Down Expand Up @@ -239,18 +250,23 @@ function renderDiff(parsedPatches: ParsedPatch[], manager?: WorkerPoolManager) {
const patchAnnotations = FAKE_DIFF_LINE_ANNOTATIONS[patchIndex] ?? [];
let hunkIndex = 0;
for (const fileDiff of parsedPatch.files) {
const editor = new Editor<LineCommentMetadata>({
__debug: true,
});
const fileAnnotations = patchAnnotations[hunkIndex];
let instance:
| FileDiff<LineCommentMetadata>
| VirtualizedFileDiff<LineCommentMetadata>;
let isEditing = false;
const options: FileDiffOptions<LineCommentMetadata> = {
theme: DEMO_THEME,
themeType,
diffStyle: unified ? 'unified' : 'split',
overflow: wrap ? 'wrap' : 'scroll',
renderAnnotation: renderDiffAnnotation,
renderHeaderMetadata() {
return createCollapsedToggle(
const collapseToggle = createToggle(
'Collapse',
instance?.options.collapsed ?? false,
(checked) => {
instance?.setOptions({
Expand All @@ -262,6 +278,32 @@ function renderDiff(parsedPatches: ParsedPatch[], manager?: WorkerPoolManager) {
}
}
);
const editableToggle = createToggle(
'Editable',
isEditing,
(checked) => {
isEditing = checked;
if (isEditing) {
editor.edit(instance);
} else {
editor.cleanUp();
}
}
);
editShortcutCallback = (): boolean | void => {
if (!isEditing) {
editableToggle.querySelector('input')?.click();
return false;
}
};
const div = document.createElement('div');
div.style.display = 'flex';
div.style.gap = '8px';
div.append(collapseToggle);
if (!fileDiff.isPartial) {
div.append(editableToggle);
}
return div;
},
lineHoverHighlight: 'both',
expansionLineCount: 10,
Expand Down Expand Up @@ -745,18 +787,43 @@ if (renderFileButton != null) {

virtualizer?.setup(globalThis.document);
const wrap = getWrapped();
const editor = new Editor<LineCommentMetadata>({
enabledQuickEdit: true,
renderQuickEdit: (ctx) => {
const div = document.createElement('div');
const button = document.createElement('button');
button.innerText = `Comment the selection`;
button.addEventListener('click', () => {
const lines = ctx.getSelectionText().split('\n');
const comment = lines
.map((line) => (line.startsWith('//') ? line : `// ${line}`))
.join('\n');
ctx.replaceSelectionText(comment);
ctx.close();
});
div.style.marginBlock = '4px';
div.appendChild(button);
return div;
},
onChange: (file, lineAnnotations) => {
console.log('change', file, lineAnnotations);
},
__debug: true,
});
const fileContainer = document.createElement(DIFFS_TAG_NAME);
wrapper.appendChild(fileContainer);
let instance:
| File<LineCommentMetadata>
| VirtualizedFile<LineCommentMetadata>;
let isEditing = false;
const options: FileOptions<LineCommentMetadata> = {
overflow: wrap ? 'wrap' : 'scroll',
theme: DEMO_THEME,
themeType: getThemeType(),
renderAnnotation,
renderHeaderMetadata() {
return createCollapsedToggle(
const collapsedToggle = createToggle(
'Collapse',
instance?.options.collapsed ?? false,
(checked) => {
instance?.setOptions({
Expand All @@ -768,6 +835,42 @@ if (renderFileButton != null) {
}
}
);
const editableToggle = createToggle(
'Editable',
isEditing,
(checked) => {
isEditing = checked;
if (isEditing) {
editor.edit(instance);
editor.setSelections([
{
start: {
line: 0,
character: 1000, // will be normalized to the end of the line(< 1000 chars)
},
end: {
line: 0,
character: 1000, // will be normalized to the end of the line(< 1000 chars)
},
direction: 'none',
},
]);
} else {
editor.cleanUp();
}
}
);
editShortcutCallback = (): boolean | void => {
if (!isEditing) {
editableToggle.querySelector('input')?.click();
return false;
}
};
const div = document.createElement('div');
div.style.display = 'flex';
div.style.gap = '8px';
div.append(collapsedToggle, editableToggle);
return div;
},

// Line selection stuff
Expand Down Expand Up @@ -955,7 +1058,34 @@ cleanButton?.addEventListener('click', () => {
cleanupInstances(container);
});

function createCollapsedToggle(
const lagRadarCheckbox = document.getElementById('lag-radar');
const radar = document.getElementById('radar');
if (lagRadarCheckbox != null && radar != null) {
const { default: lagRadar } =
// @ts-expect-error dynamic import
await import('https://mobz.github.io/lag-radar/lag-radar.js');
let dispose: (() => void) | undefined;
lagRadarCheckbox.addEventListener('change', () => {
if (
lagRadarCheckbox instanceof HTMLInputElement &&
lagRadarCheckbox.checked
) {
dispose = lagRadar({
parent: radar,
size: 100,
frames: 60,
});
radar.style.display = 'block';
} else {
dispose?.();
dispose = undefined;
radar.style.display = 'none';
}
});
}

function createToggle(
labelText: string,
checked: boolean,
onChange: (checked: boolean) => void
): HTMLElement {
Expand All @@ -968,7 +1098,7 @@ function createCollapsedToggle(
});
label.dataset.collapser = '';
label.appendChild(input);
label.append(' Collapse');
label.appendChild(document.createTextNode(` ${labelText}`));
return label;
}

Expand Down
8 changes: 8 additions & 0 deletions apps/demo/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,11 @@ diffs-container {
align-items: center;
gap: 4px;
}

[data-quick-edit-slot] button {
border-color: rgba(128, 128, 128, 0.4);

&:hover {
border-color: #646cff;
}
}
43 changes: 43 additions & 0 deletions apps/docs/app/(diffs)/_docs/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ import {
CUSTOM_HUNK_SEPARATORS_EXAMPLE,
CUSTOM_HUNK_SEPARATORS_SWITCHER,
} from '../docs/CustomHunkSeparators/constants';
import {
EDITOR_LAZY_FILE_EXAMPLE,
EDITOR_QUICK_EDIT_CONTEXT_TYPE,
EDITOR_QUICK_EDIT_EXAMPLE,
EDITOR_REACT_EXAMPLE,
EDITOR_REACT_FILE_DIFF_EXAMPLE,
EDITOR_VANILLA_FILE_DIFF_EXAMPLE,
EDITOR_VANILLA_FILE_EXAMPLE,
} from '../docs/Editor/constants';
import {
INSTALLATION_EXAMPLES,
PACKAGE_MANAGERS,
Expand Down Expand Up @@ -163,6 +172,7 @@ export default function DocsPage() {
<ReactAPISection />
<VanillaAPISection />
<CodeViewSection />
<EditorSection />
<VirtualizationSection />
<CustomHunkSeparatorsSection />
<UtilitiesSection />
Expand Down Expand Up @@ -379,6 +389,39 @@ async function CodeViewSection() {
return <ProseWrapper>{content}</ProseWrapper>;
}

async function EditorSection() {
const [
editorVanillaFileExample,
editorVanillaFileDiffExample,
editorLazyFileExample,
editorQuickEditContextType,
editorQuickEditExample,
editorReactExample,
editorReactFileDiffExample,
] = await Promise.all([
preloadFile(EDITOR_VANILLA_FILE_EXAMPLE),
preloadFile(EDITOR_VANILLA_FILE_DIFF_EXAMPLE),
preloadFile(EDITOR_LAZY_FILE_EXAMPLE),
preloadFile(EDITOR_QUICK_EDIT_CONTEXT_TYPE),
preloadFile(EDITOR_QUICK_EDIT_EXAMPLE),
preloadFile(EDITOR_REACT_EXAMPLE),
preloadFile(EDITOR_REACT_FILE_DIFF_EXAMPLE),
]);
const content = await renderMDX({
filePath: '(diffs)/docs/Editor/content.mdx',
scope: {
editorVanillaFileExample,
editorVanillaFileDiffExample,
editorLazyFileExample,
editorQuickEditContextType,
editorQuickEditExample,
editorReactExample,
editorReactFileDiffExample,
},
});
return <ProseWrapper>{content}</ProseWrapper>;
}

async function VirtualizationSection() {
const [
reactVirtualizerBasic,
Expand Down
38 changes: 38 additions & 0 deletions apps/docs/app/(diffs)/docs/Editor/ComponentTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import type { PreloadedFileResult } from '@pierre/diffs/ssr';
import { useState } from 'react';

import { DocsCodeExample } from '@/components/docs/DocsCodeExample';
import { ButtonGroup, ButtonGroupItem } from '@/components/ui/button-group';

type EditorComponentMode = 'file' | 'file-diff';

interface EditorComponentTabsProps {
fileExample: PreloadedFileResult<undefined>;
fileDiffExample: PreloadedFileResult<undefined>;
}

export function EditorComponentTabs({
fileExample,
fileDiffExample,
}: EditorComponentTabsProps) {
const [mode, setMode] = useState<EditorComponentMode>('file');

return (
<>
<ButtonGroup
value={mode}
onValueChange={(value) => setMode(value as EditorComponentMode)}
>
<ButtonGroupItem value="file">File</ButtonGroupItem>
<ButtonGroupItem value="file-diff">FileDiff</ButtonGroupItem>
</ButtonGroup>
{mode === 'file' ? (
<DocsCodeExample {...fileExample} key={mode} />
) : (
<DocsCodeExample {...fileDiffExample} key={mode} />
)}
</>
);
}
Loading
Loading