From 7c423457710d71527f7a91a296f98befcf45f73d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 25 Jun 2026 14:47:57 +0800 Subject: [PATCH 1/8] fix: align rush changelog release version --- .github/workflows/release.yml | 4 ++ .../scripts/align-rush-changelog-version.js | 61 +++++++++++++++++++ packages/vtable/CHANGELOG.json | 33 +--------- packages/vtable/CHANGELOG.md | 28 +-------- 4 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 common/scripts/align-rush-changelog-version.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 78b806c2a..afde16dde 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -307,6 +307,10 @@ jobs: if: startsWith(github.ref_name, 'release/') run: node common/scripts/install-run-rush.js version --bump + - name: Align rush changelog version (release) + if: startsWith(github.ref_name, 'release/') + run: node common/scripts/align-rush-changelog-version.js ${{ steps.semver_release.outputs.main }} + - name: Update version (release) if: startsWith(github.ref_name, 'release/') run: node common/scripts/apply-release-version.js 'none' ${{ steps.semver_release.outputs.main }} diff --git a/common/scripts/align-rush-changelog-version.js b/common/scripts/align-rush-changelog-version.js new file mode 100644 index 000000000..0298c6b2f --- /dev/null +++ b/common/scripts/align-rush-changelog-version.js @@ -0,0 +1,61 @@ +const fs = require('fs'); +const path = require('path'); + +function escapeRegExp(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function alignChangelogJson(packageDir, packageName, releaseVersion) { + const changelogJsonPath = path.join(packageDir, 'CHANGELOG.json'); + const changelog = JSON.parse(fs.readFileSync(changelogJsonPath, 'utf8')); + + if (!Array.isArray(changelog.entries) || changelog.entries.length === 0) { + throw new Error(`No changelog entries found in ${changelogJsonPath}`); + } + + changelog.entries[0].version = releaseVersion; + changelog.entries[0].tag = `${packageName}_v${releaseVersion}`; + changelog.entries = changelog.entries.filter((entry, index) => index === 0 || entry.version !== releaseVersion); + + fs.writeFileSync(changelogJsonPath, `${JSON.stringify(changelog, null, 2)}\n`); +} + +function alignChangelogMarkdown(packageDir, releaseVersion) { + const changelogMdPath = path.join(packageDir, 'CHANGELOG.md'); + const changelog = fs.readFileSync(changelogMdPath, 'utf8'); + const match = changelog.match(/^##\s+(.+)$/m); + + if (!match) { + throw new Error(`No changelog version heading found in ${changelogMdPath}`); + } + + let nextChangelog = changelog.slice(0, match.index) + `## ${releaseVersion}` + changelog.slice(match.index + match[0].length); + const duplicateHeading = new RegExp(`\\n##\\s+${escapeRegExp(releaseVersion)}\\n[\\s\\S]*?(?=\\n##\\s+|$)`, 'g'); + let seenReleaseHeading = false; + + nextChangelog = nextChangelog.replace(duplicateHeading, block => { + if (!seenReleaseHeading) { + seenReleaseHeading = true; + return block; + } + return ''; + }); + + fs.writeFileSync(changelogMdPath, nextChangelog); +} + +function run() { + const releaseVersion = process.argv[2]; + const packageName = process.argv[3] || '@visactor/vtable'; + const packageDir = process.argv[4] || path.join(__dirname, '../../packages/vtable'); + + if (!releaseVersion) { + console.error('Usage: node common/scripts/align-rush-changelog-version.js [package-name] [package-dir]'); + process.exit(1); + } + + alignChangelogJson(packageDir, packageName, releaseVersion); + alignChangelogMarkdown(packageDir, releaseVersion); +} + +run(); diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index ca03d5b37..ffc7f5a76 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -2,8 +2,8 @@ "name": "@visactor/vtable", "entries": [ { - "version": "1.26.4", - "tag": "@visactor/vtable_v1.26.4", + "version": "1.26.3", + "tag": "@visactor/vtable_v1.26.3", "date": "Thu, 25 Jun 2026 03:15:59 GMT", "comments": { "patch": [ @@ -36,35 +36,6 @@ ] } }, - { - "version": "1.26.3", - "tag": "@visactor/vtable_v1.26.3", - "date": "Tue, 16 Jun 2026 06:22:51 GMT", - "comments": { - "none": [ - { - "comment": "Merge pull request #5144 from VisActor/fix/issue-5027\n\nfix: avoid selection gap with frozen rows\n" - }, - { - "comment": "fix: preserve sticky group position after toggle\n\n" - }, - { - "comment": "fix: avoid selection gap with frozen rows\n\n" - }, - { - "comment": "fix: preserve hidden column order after filter update\n\n" - }, - { - "comment": "fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state\n\n" - } - ], - "patch": [ - { - "comment": "Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146)" - } - ] - } - }, { "version": "1.26.2", "tag": "@visactor/vtable_v1.26.2", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 3c38a148e..9fd260170 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -2,7 +2,7 @@ This log was last generated on Thu, 25 Jun 2026 03:15:59 GMT and should not be manually modified. -## 1.26.4 +## 1.26.3 Thu, 25 Jun 2026 03:15:59 GMT ### Patches @@ -21,32 +21,6 @@ Thu, 25 Jun 2026 03:15:59 GMT - fix: preserve quad style values -## 1.26.3 -Tue, 16 Jun 2026 06:22:51 GMT - -### Patches - -- Fix an issue where keyboard multi-selection with Shift plus arrow keys stopped expanding after the second cell (GitHub #5146) - -### Updates - -- Merge pull request #5144 from VisActor/fix/issue-5027 - -fix: avoid selection gap with frozen rows - -- fix: preserve sticky group position after toggle - - -- fix: avoid selection gap with frozen rows - - -- fix: preserve hidden column order after filter update - - -- fix: resolve regression from #5137 fix which broke nested header drag and exposed internal state - - - ## 1.26.2 Tue, 16 Jun 2026 01:39:41 GMT From bfc792f53f8452d5fbd5537e1b4300ffca9463a3 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 25 Jun 2026 14:55:52 +0800 Subject: [PATCH 2/8] fix: preserve shifted rush changelog entries --- .../scripts/align-rush-changelog-version.js | 103 ++++++++++++++---- packages/vtable/CHANGELOG.json | 2 +- packages/vtable/CHANGELOG.md | 2 +- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/common/scripts/align-rush-changelog-version.js b/common/scripts/align-rush-changelog-version.js index 0298c6b2f..267d16e13 100644 --- a/common/scripts/align-rush-changelog-version.js +++ b/common/scripts/align-rush-changelog-version.js @@ -1,11 +1,37 @@ const fs = require('fs'); const path = require('path'); -function escapeRegExp(value) { - return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +function readReleaseVersions() { + const releaseChangelogPath = path.join(__dirname, '../../docs/assets/changelog/en/release.md'); + const releaseChangelog = fs.readFileSync(releaseChangelogPath, 'utf8'); + + return Array.from(releaseChangelog.matchAll(/^#\s+v?(\d+\.\d+\.\d+)/gm)).map(match => match[1]); +} + +function getPreviousReleaseVersion(releaseVersion) { + const versions = readReleaseVersions(); + const releaseIndex = versions.indexOf(releaseVersion); + + if (releaseIndex === -1 || !versions[releaseIndex + 1]) { + return null; + } + + return versions[releaseIndex + 1]; +} + +function dedupeEntriesByVersion(entries) { + const seenVersions = new Set(); + + return entries.filter(entry => { + if (seenVersions.has(entry.version)) { + return false; + } + seenVersions.add(entry.version); + return true; + }); } -function alignChangelogJson(packageDir, packageName, releaseVersion) { +function alignChangelogJson(packageDir, packageName, releaseVersion, previousReleaseVersion) { const changelogJsonPath = path.join(packageDir, 'CHANGELOG.json'); const changelog = JSON.parse(fs.readFileSync(changelogJsonPath, 'utf8')); @@ -15,31 +41,68 @@ function alignChangelogJson(packageDir, packageName, releaseVersion) { changelog.entries[0].version = releaseVersion; changelog.entries[0].tag = `${packageName}_v${releaseVersion}`; - changelog.entries = changelog.entries.filter((entry, index) => index === 0 || entry.version !== releaseVersion); + + if (previousReleaseVersion && changelog.entries[1]?.version === releaseVersion) { + changelog.entries[1].version = previousReleaseVersion; + changelog.entries[1].tag = `${packageName}_v${previousReleaseVersion}`; + } + + changelog.entries = dedupeEntriesByVersion(changelog.entries); fs.writeFileSync(changelogJsonPath, `${JSON.stringify(changelog, null, 2)}\n`); } -function alignChangelogMarkdown(packageDir, releaseVersion) { +function parseMarkdownSections(changelog) { + const headingMatches = Array.from(changelog.matchAll(/^##\s+(.+)$/gm)); + + if (headingMatches.length === 0) { + return null; + } + + const prelude = changelog.slice(0, headingMatches[0].index); + const sections = headingMatches.map((match, index) => { + const start = match.index; + const bodyStart = start + match[0].length; + const end = headingMatches[index + 1]?.index ?? changelog.length; + + return { + version: match[1].trim(), + body: changelog.slice(bodyStart, end) + }; + }); + + return { prelude, sections }; +} + +function dedupeMarkdownSections(sections) { + const seenVersions = new Set(); + + return sections.filter(section => { + if (seenVersions.has(section.version)) { + return false; + } + seenVersions.add(section.version); + return true; + }); +} + +function alignChangelogMarkdown(packageDir, releaseVersion, previousReleaseVersion) { const changelogMdPath = path.join(packageDir, 'CHANGELOG.md'); const changelog = fs.readFileSync(changelogMdPath, 'utf8'); - const match = changelog.match(/^##\s+(.+)$/m); + const parsed = parseMarkdownSections(changelog); - if (!match) { + if (!parsed) { throw new Error(`No changelog version heading found in ${changelogMdPath}`); } - let nextChangelog = changelog.slice(0, match.index) + `## ${releaseVersion}` + changelog.slice(match.index + match[0].length); - const duplicateHeading = new RegExp(`\\n##\\s+${escapeRegExp(releaseVersion)}\\n[\\s\\S]*?(?=\\n##\\s+|$)`, 'g'); - let seenReleaseHeading = false; + parsed.sections[0].version = releaseVersion; - nextChangelog = nextChangelog.replace(duplicateHeading, block => { - if (!seenReleaseHeading) { - seenReleaseHeading = true; - return block; - } - return ''; - }); + if (previousReleaseVersion && parsed.sections[1]?.version === releaseVersion) { + parsed.sections[1].version = previousReleaseVersion; + } + + const sections = dedupeMarkdownSections(parsed.sections); + const nextChangelog = parsed.prelude + sections.map(section => `## ${section.version}${section.body}`).join(''); fs.writeFileSync(changelogMdPath, nextChangelog); } @@ -54,8 +117,10 @@ function run() { process.exit(1); } - alignChangelogJson(packageDir, packageName, releaseVersion); - alignChangelogMarkdown(packageDir, releaseVersion); + const previousReleaseVersion = getPreviousReleaseVersion(releaseVersion); + + alignChangelogJson(packageDir, packageName, releaseVersion, previousReleaseVersion); + alignChangelogMarkdown(packageDir, releaseVersion, previousReleaseVersion); } run(); diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index ffc7f5a76..92b90ac38 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -39,7 +39,7 @@ { "version": "1.26.2", "tag": "@visactor/vtable_v1.26.2", - "date": "Tue, 16 Jun 2026 01:39:41 GMT", + "date": "Tue, 16 Jun 2026 06:22:51 GMT", "comments": { "none": [ { diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index 9fd260170..e66b1ad64 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -22,7 +22,7 @@ Thu, 25 Jun 2026 03:15:59 GMT - fix: preserve quad style values ## 1.26.2 -Tue, 16 Jun 2026 01:39:41 GMT +Tue, 16 Jun 2026 06:22:51 GMT ### Patches From 04b58135065653cdd72ad867b82a4077f97c0e20 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Thu, 25 Jun 2026 15:02:53 +0800 Subject: [PATCH 3/8] fix: generate release changelog from current version --- .github/workflows/release.yml | 24 +++- .../scripts/align-rush-changelog-version.js | 126 ------------------ 2 files changed, 17 insertions(+), 133 deletions(-) delete mode 100644 common/scripts/align-rush-changelog-version.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afde16dde..34428165f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -303,17 +303,27 @@ jobs: fi done - - name: Generate rush version (release) + - name: Reset version-policy base version (release) if: startsWith(github.ref_name, 'release/') - run: node common/scripts/install-run-rush.js version --bump + run: | + node <<'NODE' + const fs = require('fs'); - - name: Align rush changelog version (release) - if: startsWith(github.ref_name, 'release/') - run: node common/scripts/align-rush-changelog-version.js ${{ steps.semver_release.outputs.main }} + const packageJson = JSON.parse(fs.readFileSync('packages/vtable/package.json', 'utf8')); + const policiesPath = 'common/config/rush/version-policies.json'; + const policies = JSON.parse(fs.readFileSync(policiesPath, 'utf8')); - - name: Update version (release) + if (!policies[0]) { + throw new Error('Missing version policy.'); + } + + policies[0].version = packageJson.version; + fs.writeFileSync(policiesPath, JSON.stringify(policies)); + NODE + + - name: Generate rush version (release) if: startsWith(github.ref_name, 'release/') - run: node common/scripts/apply-release-version.js 'none' ${{ steps.semver_release.outputs.main }} + run: node common/scripts/install-run-rush.js version --bump - name: Build packages env: diff --git a/common/scripts/align-rush-changelog-version.js b/common/scripts/align-rush-changelog-version.js deleted file mode 100644 index 267d16e13..000000000 --- a/common/scripts/align-rush-changelog-version.js +++ /dev/null @@ -1,126 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -function readReleaseVersions() { - const releaseChangelogPath = path.join(__dirname, '../../docs/assets/changelog/en/release.md'); - const releaseChangelog = fs.readFileSync(releaseChangelogPath, 'utf8'); - - return Array.from(releaseChangelog.matchAll(/^#\s+v?(\d+\.\d+\.\d+)/gm)).map(match => match[1]); -} - -function getPreviousReleaseVersion(releaseVersion) { - const versions = readReleaseVersions(); - const releaseIndex = versions.indexOf(releaseVersion); - - if (releaseIndex === -1 || !versions[releaseIndex + 1]) { - return null; - } - - return versions[releaseIndex + 1]; -} - -function dedupeEntriesByVersion(entries) { - const seenVersions = new Set(); - - return entries.filter(entry => { - if (seenVersions.has(entry.version)) { - return false; - } - seenVersions.add(entry.version); - return true; - }); -} - -function alignChangelogJson(packageDir, packageName, releaseVersion, previousReleaseVersion) { - const changelogJsonPath = path.join(packageDir, 'CHANGELOG.json'); - const changelog = JSON.parse(fs.readFileSync(changelogJsonPath, 'utf8')); - - if (!Array.isArray(changelog.entries) || changelog.entries.length === 0) { - throw new Error(`No changelog entries found in ${changelogJsonPath}`); - } - - changelog.entries[0].version = releaseVersion; - changelog.entries[0].tag = `${packageName}_v${releaseVersion}`; - - if (previousReleaseVersion && changelog.entries[1]?.version === releaseVersion) { - changelog.entries[1].version = previousReleaseVersion; - changelog.entries[1].tag = `${packageName}_v${previousReleaseVersion}`; - } - - changelog.entries = dedupeEntriesByVersion(changelog.entries); - - fs.writeFileSync(changelogJsonPath, `${JSON.stringify(changelog, null, 2)}\n`); -} - -function parseMarkdownSections(changelog) { - const headingMatches = Array.from(changelog.matchAll(/^##\s+(.+)$/gm)); - - if (headingMatches.length === 0) { - return null; - } - - const prelude = changelog.slice(0, headingMatches[0].index); - const sections = headingMatches.map((match, index) => { - const start = match.index; - const bodyStart = start + match[0].length; - const end = headingMatches[index + 1]?.index ?? changelog.length; - - return { - version: match[1].trim(), - body: changelog.slice(bodyStart, end) - }; - }); - - return { prelude, sections }; -} - -function dedupeMarkdownSections(sections) { - const seenVersions = new Set(); - - return sections.filter(section => { - if (seenVersions.has(section.version)) { - return false; - } - seenVersions.add(section.version); - return true; - }); -} - -function alignChangelogMarkdown(packageDir, releaseVersion, previousReleaseVersion) { - const changelogMdPath = path.join(packageDir, 'CHANGELOG.md'); - const changelog = fs.readFileSync(changelogMdPath, 'utf8'); - const parsed = parseMarkdownSections(changelog); - - if (!parsed) { - throw new Error(`No changelog version heading found in ${changelogMdPath}`); - } - - parsed.sections[0].version = releaseVersion; - - if (previousReleaseVersion && parsed.sections[1]?.version === releaseVersion) { - parsed.sections[1].version = previousReleaseVersion; - } - - const sections = dedupeMarkdownSections(parsed.sections); - const nextChangelog = parsed.prelude + sections.map(section => `## ${section.version}${section.body}`).join(''); - - fs.writeFileSync(changelogMdPath, nextChangelog); -} - -function run() { - const releaseVersion = process.argv[2]; - const packageName = process.argv[3] || '@visactor/vtable'; - const packageDir = process.argv[4] || path.join(__dirname, '../../packages/vtable'); - - if (!releaseVersion) { - console.error('Usage: node common/scripts/align-rush-changelog-version.js [package-name] [package-dir]'); - process.exit(1); - } - - const previousReleaseVersion = getPreviousReleaseVersion(releaseVersion); - - alignChangelogJson(packageDir, packageName, releaseVersion, previousReleaseVersion); - alignChangelogMarkdown(packageDir, releaseVersion, previousReleaseVersion); -} - -run(); From 181ea397b58fd1a49e3bb953810ba018212c0381 Mon Sep 17 00:00:00 2001 From: Semlaw Date: Mon, 6 Jul 2026 14:17:03 +0800 Subject: [PATCH 4/8] feat: support video first frame snapshot --- .../cell-type/video-cell-first-frame.test.ts | 240 ++++++++++++++++++ .../group-creater/cell-type/image-cell.ts | 2 +- .../group-creater/cell-type/video-cell.ts | 196 +++++++++++++- packages/vtable/src/ts-types/base-table.ts | 6 + 4 files changed, 435 insertions(+), 9 deletions(-) create mode 100644 packages/vtable/__tests__/cell-type/video-cell-first-frame.test.ts diff --git a/packages/vtable/__tests__/cell-type/video-cell-first-frame.test.ts b/packages/vtable/__tests__/cell-type/video-cell-first-frame.test.ts new file mode 100644 index 000000000..2080a366f --- /dev/null +++ b/packages/vtable/__tests__/cell-type/video-cell-first-frame.test.ts @@ -0,0 +1,240 @@ +// @ts-nocheck +import { createVideoCellGroup } from '../../src/scenegraph/group-creater/cell-type/video-cell'; +import { updateImageCellContentWhileResize } from '../../src/scenegraph/group-creater/cell-type/image-cell'; +import { registerForVrender } from '../../src/vrender'; +import * as icons from '../../src/icons'; + +global.__VERSION__ = 'none'; + +registerForVrender(); + +describe('video cell first frame snapshot', () => { + const originalCreateElement = document.createElement.bind(document); + let createdVideo: HTMLVideoElement; + let drawImage: jest.Mock; + + function createTable(customConfig?: Record) { + return { + options: { + customConfig + }, + _getCellStyle: jest.fn(() => ({})), + getCellValue: jest.fn(() => 'https://example.com/video.mp4'), + colCount: 1, + rowCount: 1, + theme: { + cellInnerBorder: true, + frameStyle: {} + }, + getCellIcons: jest.fn(), + scenegraph: { + updateNextFrame: jest.fn(), + highPerformanceGetCell: jest.fn(), + getCell: jest.fn() + } + }; + } + + function createCell(customConfig?: Record, size = { width: 200, height: 120 }) { + const table = createTable(customConfig); + const cellGroup = createVideoCellGroup( + undefined, + 0, + 0, + 0, + 0, + size.width, + size.height, + false, + false, + [0, 0, 0, 0], + 'left', + 'middle', + false, + table, + { + group: {}, + text: {} + }, + undefined, + false + ); + table.scenegraph.getCell.mockReturnValue(cellGroup); + + return { + table, + cellGroup, + image: cellGroup.getChildByName('image', true), + video: createdVideo + }; + } + + beforeEach(() => { + drawImage = jest.fn(); + jest.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({ + drawImage + } as any); + jest.spyOn(document, 'createElement').mockImplementation((tagName: string, options?: ElementCreationOptions) => { + const element = originalCreateElement(tagName, options); + if (tagName.toLowerCase() === 'video') { + createdVideo = element as HTMLVideoElement; + Object.defineProperties(createdVideo, { + videoWidth: { + value: 640, + configurable: true + }, + videoHeight: { + value: 360, + configurable: true + }, + pause: { + value: jest.fn(), + configurable: true + }, + load: { + value: jest.fn(), + configurable: true + } + }); + } + return element; + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('keeps existing video rendering when first frame snapshot is not enabled', () => { + const { image, video } = createCell(); + + video.dispatchEvent(new Event('loadeddata')); + + expect(image.attribute.image).toBe(video); + expect(drawImage).not.toHaveBeenCalled(); + expect(video.pause).not.toHaveBeenCalled(); + expect(video.load).not.toHaveBeenCalled(); + }); + + it('uses a canvas snapshot and releases the video when enabled', () => { + const { image, table, video } = createCell({ + videoFirstFrameSnapshot: true, + videoFirstFrameMaxCanvasSize: 128 + }); + + expect(video.getAttribute('preload')).toBe('auto'); + expect(video.getAttribute('src')).toBe('https://example.com/video.mp4'); + + video.dispatchEvent(new Event('loadeddata')); + + expect(drawImage).toHaveBeenCalledWith(video, 0, 0, 128, 77); + expect(image.attribute.image).toBeInstanceOf(HTMLCanvasElement); + expect((image.attribute.image as HTMLCanvasElement).width).toBe(128); + expect((image.attribute.image as HTMLCanvasElement).height).toBe(77); + expect(video.pause).toHaveBeenCalledTimes(1); + expect(video.hasAttribute('src')).toBe(false); + expect(video.load).toHaveBeenCalledTimes(1); + expect(table.scenegraph.updateNextFrame).toHaveBeenCalled(); + }); + + it('releases the video and redraws on load error', () => { + const { table, video } = createCell({ + videoFirstFrameSnapshot: true + }); + + video.dispatchEvent(new Event('error')); + + expect(video.pause).toHaveBeenCalledTimes(1); + expect(video.hasAttribute('src')).toBe(false); + expect(video.load).toHaveBeenCalledTimes(1); + expect(table.scenegraph.updateNextFrame).toHaveBeenCalled(); + }); + + it('keeps the video damage image aspect ratio immediately after load error', () => { + const { image, video } = createCell( + { + videoFirstFrameSnapshot: true + }, + { width: 360, height: 40 } + ); + const regedIcons = icons.get(); + const damageImage = regedIcons.video_damage_pic + ? (regedIcons.video_damage_pic as any).svg + : (regedIcons.damage_pic as any).svg; + + image.resources.set(damageImage, { + state: 'success', + data: { + width: 24, + height: 24 + } + }); + video.dispatchEvent(new Event('error')); + + expect(image.attribute.image).toBe(damageImage); + expect(image.attribute.x).toBe(0); + expect(image.attribute.y).toBe(0); + expect(image.attribute.width).toBe(40); + expect(image.attribute.height).toBe(40); + }); + + it('keeps custom video damage svg aspect ratio before the icon resource is cached', () => { + const originalVideoDamageIcon = (icons.icons as any).video_damage_pic; + (icons.icons as any).video_damage_pic = { + type: 'svg', + svg: '', + name: 'video_damage_pic', + positionType: 'right' + }; + + try { + const { image, video } = createCell( + { + videoFirstFrameSnapshot: true + }, + { width: 360, height: 40 } + ); + + video.dispatchEvent(new Event('error')); + + expect(image.attribute.image).toBe((icons.icons as any).video_damage_pic.svg); + expect(image.attribute.x).toBe(0); + expect(image.attribute.y).toBe(0); + expect(image.attribute.width).toBe(40); + expect(image.attribute.height).toBe(40); + } finally { + if (originalVideoDamageIcon) { + (icons.icons as any).video_damage_pic = originalVideoDamageIcon; + } else { + delete (icons.icons as any).video_damage_pic; + } + } + }); + + it('keeps the video damage image aspect ratio when the column resizes after load error', () => { + const { cellGroup, image, table, video } = createCell({ + videoFirstFrameSnapshot: true + }); + const regedIcons = icons.get(); + const damageImage = regedIcons.video_damage_pic + ? (regedIcons.video_damage_pic as any).svg + : (regedIcons.damage_pic as any).svg; + + video.dispatchEvent(new Event('error')); + image.resources.set(damageImage, { + state: 'success', + data: { + width: 24, + height: 24 + } + }); + cellGroup.setAttribute('width', 360); + updateImageCellContentWhileResize(cellGroup, 0, 0, 160, 0, table); + + expect(image.attribute.image).toBe(damageImage); + expect(image.attribute.x).toBe(0); + expect(image.attribute.y).toBe(0); + expect(image.attribute.width).toBe(120); + expect(image.attribute.height).toBe(120); + }); +}); diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts index 8cffa12ad..0402bf657 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/image-cell.ts @@ -344,7 +344,7 @@ export function updateImageCellContentWhileResize( const leftIconWidth = (cellGroup as any)._cellLeftIconWidth ?? 0; const rightIconWidth = (cellGroup as any)._cellRightIconWidth ?? 0; - if ((image as any).keepAspectRatio) { + if ((image as any).keepAspectRatio || isDamagePic(image)) { const { width: imageWidth, height: imageHeight } = calcKeepAspectRatioSize( originImage.width || (originImage as any).videoWidth, originImage.height || (originImage as any).videoHeight, diff --git a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts index dea524608..6fb63e534 100644 --- a/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts +++ b/packages/vtable/src/scenegraph/group-creater/cell-type/video-cell.ts @@ -23,6 +23,89 @@ import { dealWithIconLayout } from '../../utils/text-icon-layout'; const regedIcons = icons.get(); +function releaseVideoResource(video: HTMLVideoElement): void { + try { + video.pause(); + } catch (err) { + // ignore media cleanup errors + } + video.removeAttribute('src'); + try { + video.load(); + } catch (err) { + // ignore media cleanup errors + } +} + +function getVideoFirstFrameTimeout(table: BaseTableAPI): number { + const timeout = table.options.customConfig?.videoFirstFrameTimeout; + return typeof timeout === 'number' && timeout >= 0 ? timeout : 8000; +} + +function getVideoFirstFrameMaxCanvasSize(table: BaseTableAPI): number { + const maxCanvasSize = table.options.customConfig?.videoFirstFrameMaxCanvasSize; + return typeof maxCanvasSize === 'number' && maxCanvasSize > 0 ? maxCanvasSize : 512; +} + +function snapshotVideoFirstFrame(video: HTMLVideoElement, image: IImage, table: BaseTableAPI): boolean { + const displayWidth = image.attribute.width; + const displayHeight = image.attribute.height; + if ( + typeof displayWidth !== 'number' || + typeof displayHeight !== 'number' || + displayWidth <= 0 || + displayHeight <= 0 + ) { + return false; + } + + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (!context) { + return false; + } + + const dpr = Math.min((typeof window === 'undefined' ? 1 : window.devicePixelRatio) || 1, 2); + const maxSize = getVideoFirstFrameMaxCanvasSize(table); + const scale = Math.min(dpr, maxSize / Math.max(displayWidth, displayHeight)); + canvas.width = Math.max(1, Math.ceil(displayWidth * scale)); + canvas.height = Math.max(1, Math.ceil(displayHeight * scale)); + canvas.style.width = `${displayWidth}px`; + canvas.style.height = `${displayHeight}px`; + + try { + context.drawImage(video, 0, 0, canvas.width, canvas.height); + image.setAttributes({ image: canvas as any }); + return true; + } catch (err) { + return false; + } +} + +function getSvgSize(svg: string): { width: number; height: number } | undefined { + const svgTag = svg.match(/]*>/i)?.[0]; + if (!svgTag) { + return undefined; + } + + const widthMatch = svgTag.match(/\bwidth=["']?([\d.]+)/i); + const heightMatch = svgTag.match(/\bheight=["']?([\d.]+)/i); + const width = widthMatch ? Number(widthMatch[1]) : undefined; + const height = heightMatch ? Number(heightMatch[1]) : undefined; + if (width > 0 && height > 0) { + return { width, height }; + } + + const viewBoxMatch = svgTag.match(/\bviewBox=["']\s*[-\d.]+\s+[-\d.]+\s+([\d.]+)\s+([\d.]+)\s*["']/i); + const viewBoxWidth = viewBoxMatch ? Number(viewBoxMatch[1]) : undefined; + const viewBoxHeight = viewBoxMatch ? Number(viewBoxMatch[2]) : undefined; + if (viewBoxWidth > 0 && viewBoxHeight > 0) { + return { width: viewBoxWidth, height: viewBoxHeight }; + } + + return undefined; +} + export function createVideoCellGroup( columnGroup: Group, xOrigin: number, @@ -162,7 +245,100 @@ export function createVideoCellGroup( // video const value = table.getCellValue(col, row); const video = document.createElement('video'); + video.muted = true; + video.playsInline = true; + const shouldSnapshot = table.options.customConfig?.videoFirstFrameSnapshot === true; + let loadTimer: ReturnType | undefined; + let videoReleased = false; + + const clearVideoLoadTimer = (): void => { + if (loadTimer !== undefined) { + clearTimeout(loadTimer); + loadTimer = undefined; + } + }; + const releaseCurrentVideo = (): void => { + if (videoReleased) { + return; + } + videoReleased = true; + clearVideoLoadTimer(); + releaseVideoResource(video); + }; + const isCurrentImage = (): boolean => cellGroup.getChildByName('image', true) === image; + const setVideoDamageImage = (): void => { + const regedIcons = icons.get(); + const damageIcon = regedIcons.video_damage_pic || regedIcons.damage_pic; + const damageImage = (damageIcon as any).svg; + image.setAttributes({ + image: damageImage + } as any); + const originImage = image.resources?.get(damageImage)?.data; + const svgSize = typeof damageImage === 'string' ? getSvgSize(damageImage) : undefined; + const originWidth = originImage?.width || (damageIcon as any).width || svgSize?.width || 24; + const originHeight = originImage?.height || (damageIcon as any).height || svgSize?.height || 24; + const { width: cellWidth, height: cellHeight, isMerge } = getCellRange(cellGroup, table); + const availableWidth = cellWidth - padding[1] - padding[3]; + const availableHeight = cellHeight - padding[0] - padding[2]; + + if (originWidth > 0 && originHeight > 0 && availableWidth > 0 && availableHeight > 0) { + const { width: imageWidth, height: imageHeight } = calcKeepAspectRatioSize( + originWidth, + originHeight, + availableWidth, + availableHeight + ); + const pos = calcStartPosition( + 0, + 0, + cellWidth, + cellHeight, + imageWidth, + imageHeight, + textAlign, + textBaseline, + padding + ); + + image.setAttributes({ + x: pos.x, + y: pos.y, + width: imageWidth, + height: imageHeight + }); + + if (isMerge) { + updateImageDxDy( + cellGroup.mergeStartCol, + cellGroup.mergeEndCol, + cellGroup.mergeStartRow, + cellGroup.mergeEndRow, + table + ); + } + } + }; + const handleVideoLoadFail = (): void => { + if (videoReleased) { + return; + } + if (isCurrentImage()) { + setVideoDamageImage(); + table.scenegraph.updateNextFrame(); + } + if (shouldSnapshot) { + releaseCurrentVideo(); + } + }; video.addEventListener('loadeddata', (): void => { + clearVideoLoadTimer(); + if (videoReleased) { + return; + } + if (!isCurrentImage()) { + releaseCurrentVideo(); + return; + } if (imageAutoSizing) { _adjustWidthHeight(col, row, video.videoWidth, video.videoHeight, table.scenegraph, padding, cellGroup); } @@ -248,17 +424,14 @@ export function createVideoCellGroup( }); playIcon.name = 'play-icon'; cellGroup.appendChild(playIcon); + if (shouldSnapshot && snapshotVideoFirstFrame(video, image, table)) { + releaseCurrentVideo(); + } // 触发重绘 table.scenegraph.updateNextFrame(); }); - video.onerror = (): void => { - const regedIcons = icons.get(); - (image as any).image = regedIcons.video_damage_pic - ? (regedIcons.video_damage_pic as any).svg - : (regedIcons.damage_pic as any).svg; - }; - video.src = value; - video.setAttribute('preload', 'auto'); + video.addEventListener('error', handleVideoLoadFail); + video.addEventListener('abort', handleVideoLoadFail); const image: IImage = createImage({ x: padding[3], @@ -291,6 +464,13 @@ export function createVideoCellGroup( table.scenegraph.updateNextFrame(); } }; + + video.setAttribute('preload', 'auto'); + video.src = value; + const timeout = getVideoFirstFrameTimeout(table); + if (shouldSnapshot && timeout > 0) { + loadTimer = setTimeout(handleVideoLoadFail, timeout); + } return cellGroup; } diff --git a/packages/vtable/src/ts-types/base-table.ts b/packages/vtable/src/ts-types/base-table.ts index 4c9811d85..2a4e8740d 100644 --- a/packages/vtable/src/ts-types/base-table.ts +++ b/packages/vtable/src/ts-types/base-table.ts @@ -639,6 +639,12 @@ export interface BaseTableConstructorOptions { // 图片资源请求时是否使用anonymous模式 imageAnonymous?: boolean; + // 视频单元格首帧绘制后是否替换为canvas快照并释放video资源 + videoFirstFrameSnapshot?: boolean; + // 视频单元格等待首帧的超时时间,单位ms,默认8000 + videoFirstFrameTimeout?: number; + // 视频单元格首帧快照canvas的最大边长,默认512 + videoFirstFrameMaxCanvasSize?: number; // 滚动到边界是否继续触发滚动事件 scrollEventAlwaysTrigger?: boolean; From 204083562087ed9cdc8ee4bdfe9640b6bc304057 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 6 Jul 2026 14:59:44 +0800 Subject: [PATCH 5/8] chore: add video snapshot changefile --- ...t-video-first-frame-snapshot_2026-07-06-15-35.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json diff --git a/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json b/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json new file mode 100644 index 000000000..81125dc55 --- /dev/null +++ b/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@visactor/vtable", + "comment": "Support rendering video cells from a first-frame canvas snapshot to release video resources after loading.", + "type": "minor" + } + ], + "packageName": "@visactor/vtable", + "email": "keepjog@163.com" +} From 0f22127a8754b5ed09c97b98690a7f6787641ba5 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Mon, 6 Jul 2026 17:24:26 +0800 Subject: [PATCH 6/8] chore: remove unused pre-release workflow --- .github/workflows/pre-release.yml | 72 ------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 .github/workflows/pre-release.yml diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml deleted file mode 100644 index 9a1263227..000000000 --- a/.github/workflows/pre-release.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Pre-release CI - -on: - push: - branches: - - 'pre-release/[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+' - - 'pre-release/[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' - - 'pre-release/[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+' - -jobs: - build: - runs-on: macOS-latest # 如果用了electron,记得改成 macOS-latest - - permissions: - contents: write - - strategy: - matrix: - node-version: [18.x] - # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - cache-dependency-path: './common/config/rush/pnpm-lock.yaml' - - # Install rush - - name: Install rush - run: node common/scripts/install-run-rush.js install --bypass-policy - - - name: Parse semver version from branch name - id: semver_parser - uses: xile611/read-package-version-action@v2.1 - with: - path: packages/vtable - semver_string: ${{ github.ref_name }} - semver_pattern: '^pre-release/(.*)$' # ^v?(.*)$ by default - - - name: Apply prereleaseName - run: node common/scripts/apply-prerelease-version.js ${{ steps.semver_parser.outputs.pre_release_name }} ${{ steps.semver_parser.outputs.main }} - - - name: Build packages - env: - NODE_OPTIONS: "--max_old_space_size=4096" - run: node common/scripts/install-run-rush.js build --only tag:package - - - name: Publish to npm - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - NPM_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - run: node common/scripts/install-run-rush.js publish --publish --include-all --tag ${{ steps.semver_parser.outputs.pre_release_type }} - - - name: Update shrinkwrap - run: node common/scripts/install-run-rush.js update - - - name: Get npm version - id: package-version - uses: xile611/read-package-version-action@main - with: - path: packages/vtable - - - name: Commit & Push changes - uses: actions-js/push@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - message: 'build: prelease version ${{ steps.package-version.outputs.current_version }}' - branch: ${{ github.ref_name }} - author_name: ${{ github.actor }} From f37428b890c19deaa898f0d200f340de445c21b8 Mon Sep 17 00:00:00 2001 From: fangsmile Date: Mon, 6 Jul 2026 09:48:00 +0000 Subject: [PATCH 7/8] build: release version 1.26.4 [skip ci] --- ...first-frame-snapshot_2026-07-06-15-35.json | 11 ---- common/config/rush/pnpm-lock.yaml | 58 +++++++++---------- docs/assets/changelog/en/release.md | 12 ++++ docs/assets/changelog/zh/release.md | 12 ++++ docs/package.json | 18 +++--- packages/openinula-vtable/package.json | 4 +- packages/react-vtable/package.json | 4 +- packages/vtable-calendar/package.json | 4 +- packages/vtable-editors/package.json | 2 +- packages/vtable-export/package.json | 4 +- packages/vtable-gantt/package.json | 6 +- packages/vtable-plugins/package.json | 8 +-- packages/vtable-search/package.json | 4 +- packages/vtable-sheet/package.json | 8 +-- packages/vtable/CHANGELOG.json | 12 ++++ packages/vtable/CHANGELOG.md | 9 ++- packages/vtable/package.json | 4 +- packages/vue-vtable/package.json | 4 +- tools/bugserver-trigger/package.json | 10 ++-- 19 files changed, 113 insertions(+), 81 deletions(-) delete mode 100644 common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json diff --git a/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json b/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json deleted file mode 100644 index 81125dc55..000000000 --- a/common/changes/@visactor/vtable/feat-video-first-frame-snapshot_2026-07-06-15-35.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "@visactor/vtable", - "comment": "Support rendering video cells from a first-frame canvas snapshot to release video resources after loading.", - "type": "minor" - } - ], - "packageName": "@visactor/vtable", - "email": "keepjog@163.com" -} diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 1e45fae1a..1d807dbfd 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -19,34 +19,34 @@ importers: specifier: ^2.11.0 version: 2.57.0(vue@3.5.31(typescript@4.9.5)) '@visactor/openinula-vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/openinula-vtable '@visactor/react-vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/react-vtable '@visactor/vchart': specifier: 2.0.13-alpha.10 version: 2.0.13-alpha.10 '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable-editors '@visactor/vtable-export': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable-export '@visactor/vtable-gantt': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable-gantt '@visactor/vtable-search': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable-search '@visactor/vtable-sheet': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vtable-sheet '@visactor/vue-vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../packages/vue-vtable '@visactor/vutils': specifier: ~1.0.17 @@ -140,7 +140,7 @@ importers: ../../packages/openinula-vtable: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -270,7 +270,7 @@ importers: ../../packages/react-vtable: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -445,7 +445,7 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-editors '@visactor/vutils': specifier: ~1.0.17 @@ -626,7 +626,7 @@ importers: ../../packages/vtable-calendar: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -849,7 +849,7 @@ importers: specifier: 2.0.7 version: 2.0.7 '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1009,10 +1009,10 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-editors '@visactor/vutils': specifier: ~1.0.17 @@ -1245,13 +1245,13 @@ importers: specifier: 2.0.13-alpha.10 version: 2.0.13-alpha.10 '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-editors '@visactor/vtable-gantt': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-gantt '@vitejs/plugin-react': specifier: 3.1.0 @@ -1350,7 +1350,7 @@ importers: ../../packages/vtable-search: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1504,13 +1504,13 @@ importers: specifier: ~1.0.17 version: 1.0.23 '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-editors '@visactor/vtable-plugins': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable-plugins '@visactor/vutils': specifier: ~1.0.17 @@ -1649,7 +1649,7 @@ importers: ../../packages/vue-vtable: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../vtable '@visactor/vutils': specifier: ~1.0.17 @@ -1830,19 +1830,19 @@ importers: ../../tools/bugserver-trigger: dependencies: '@visactor/vtable': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../../packages/vtable '@visactor/vtable-editors': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../../packages/vtable-editors '@visactor/vtable-gantt': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../../packages/vtable-gantt '@visactor/vtable-plugins': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../../packages/vtable-plugins '@visactor/vtable-sheet': - specifier: workspace:1.26.3 + specifier: workspace:1.26.4 version: link:../../packages/vtable-sheet devDependencies: '@internal/bundler': diff --git a/docs/assets/changelog/en/release.md b/docs/assets/changelog/en/release.md index 818ee386c..1c7dbc827 100644 --- a/docs/assets/changelog/en/release.md +++ b/docs/assets/changelog/en/release.md @@ -1,3 +1,15 @@ +# v1.26.4 + +2026-07-06 + +**🆕 New Features** + +- **@visactor/vtable**: Support rendering video cells from a first-frame canvas snapshot to release video resources after loading. + +**Full Changelog**: https://github.com/VisActor/VTable/compare/v1.26.3...v1.26.4 + +[more detail about v1.26.4](https://github.com/VisActor/VTable/releases/tag/v1.26.4) + # v1.26.3 2026-06-25 diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index e0d795254..5cebb005a 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -1,3 +1,15 @@ +# v1.26.4 + +2026-07-06 + +**🆕 新增功能** + +- **@visactor/vtable**: 支持 rendering video cells from a first-frame canvas snapshot release video resources after loading. + +**Full Changelog**: https://github.com/VisActor/VTable/compare/v1.26.3...v1.26.4 + +[更多详情请查看 v1.26.4](https://github.com/VisActor/VTable/releases/tag/v1.26.4) + # v1.26.3 2026-06-25 diff --git a/docs/package.json b/docs/package.json index f27736945..752e0c7dd 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,15 +12,15 @@ "dependencies": { "@arco-design/web-react": "2.66.12", "@arco-design/web-vue": "^2.11.0", - "@visactor/vtable": "workspace:1.26.3", - "@visactor/vtable-gantt": "workspace:1.26.3", - "@visactor/react-vtable": "workspace:1.26.3", - "@visactor/vue-vtable": "workspace:1.26.3", - "@visactor/openinula-vtable": "workspace:1.26.3", - "@visactor/vtable-editors": "workspace:1.26.3", - "@visactor/vtable-export": "workspace:1.26.3", - "@visactor/vtable-search": "workspace:1.26.3", - "@visactor/vtable-sheet": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", + "@visactor/vtable-gantt": "workspace:1.26.4", + "@visactor/react-vtable": "workspace:1.26.4", + "@visactor/vue-vtable": "workspace:1.26.4", + "@visactor/openinula-vtable": "workspace:1.26.4", + "@visactor/vtable-editors": "workspace:1.26.4", + "@visactor/vtable-export": "workspace:1.26.4", + "@visactor/vtable-search": "workspace:1.26.4", + "@visactor/vtable-sheet": "workspace:1.26.4", "buble": "^0.20.0", "@visactor/vchart": "2.0.13-alpha.10", "markdown-it": "^13.0.0", diff --git a/packages/openinula-vtable/package.json b/packages/openinula-vtable/package.json index 919ad191f..06b70d8a3 100644 --- a/packages/openinula-vtable/package.json +++ b/packages/openinula-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/openinula-vtable", - "version": "1.26.3", + "version": "1.26.4", "description": "The openinula version of VTable", "keywords": [ "openinula", @@ -44,7 +44,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17" }, "devDependencies": { diff --git a/packages/react-vtable/package.json b/packages/react-vtable/package.json index daa67cbc2..ba75a1a5e 100644 --- a/packages/react-vtable/package.json +++ b/packages/react-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/react-vtable", - "version": "1.26.3", + "version": "1.26.4", "description": "The react version of VTable", "keywords": [ "react", @@ -57,7 +57,7 @@ "react-dom": "^18.2.0 || ^19.0.0" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17", "react-is": "^18.2.0", "react-reconciler": "0.29.0" diff --git a/packages/vtable-calendar/package.json b/packages/vtable-calendar/package.json index 8d26224b0..1d2fd909a 100644 --- a/packages/vtable-calendar/package.json +++ b/packages/vtable-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-calendar", - "version": "1.26.3", + "version": "1.26.4", "description": "The calendar component of VTable", "author": { "name": "VisActor", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17", "date-fns": "3.6.0" }, diff --git a/packages/vtable-editors/package.json b/packages/vtable-editors/package.json index 81e46f1a1..d915b63f8 100644 --- a/packages/vtable-editors/package.json +++ b/packages/vtable-editors/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-editors", - "version": "1.26.3", + "version": "1.26.4", "description": "", "sideEffects": false, "main": "cjs/index.js", diff --git a/packages/vtable-export/package.json b/packages/vtable-export/package.json index 3a99af639..d1e692f8c 100644 --- a/packages/vtable-export/package.json +++ b/packages/vtable-export/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-export", - "version": "1.26.3", + "version": "1.26.4", "description": "The export util of VTable", "author": { "name": "VisActor", @@ -37,7 +37,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17", "file-saver": "2.0.5", "@types/file-saver": "2.0.7", diff --git a/packages/vtable-gantt/package.json b/packages/vtable-gantt/package.json index 13838cd9b..5da89165a 100644 --- a/packages/vtable-gantt/package.json +++ b/packages/vtable-gantt/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-gantt", - "version": "1.26.3", + "version": "1.26.4", "description": "canvas table width high performance", "keywords": [ "vtable-gantt", @@ -50,8 +50,8 @@ "fix-memory-limit": "cross-env LIMIT=10240 increase-memory-limit" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", - "@visactor/vtable-editors": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", + "@visactor/vtable-editors": "workspace:1.26.4", "@visactor/vutils": "~1.0.17", "@visactor/vscale": "~1.0.17", "@visactor/vdataset": "~1.0.17", diff --git a/packages/vtable-plugins/package.json b/packages/vtable-plugins/package.json index ce9184028..4e49a9db8 100644 --- a/packages/vtable-plugins/package.json +++ b/packages/vtable-plugins/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-plugins", - "version": "1.26.3", + "version": "1.26.4", "description": "The search util of VTable", "author": { "name": "VisActor", @@ -61,9 +61,9 @@ "devDependencies": { "cross-env": "^7.0.3", "increase-memory-limit": "^1.0.7", - "@visactor/vtable": "workspace:1.26.3", - "@visactor/vtable-editors": "workspace:1.26.3", - "@visactor/vtable-gantt": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", + "@visactor/vtable-editors": "workspace:1.26.4", + "@visactor/vtable-gantt": "workspace:1.26.4", "@visactor/vchart": "2.0.13-alpha.10", "@internal/bundler": "workspace:*", "@internal/eslint-config": "workspace:*", diff --git a/packages/vtable-search/package.json b/packages/vtable-search/package.json index fb4908e66..0002f0190 100644 --- a/packages/vtable-search/package.json +++ b/packages/vtable-search/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-search", - "version": "1.26.3", + "version": "1.26.4", "description": "The search util of VTable", "author": { "name": "VisActor", @@ -36,7 +36,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17" }, "devDependencies": { diff --git a/packages/vtable-sheet/package.json b/packages/vtable-sheet/package.json index 69f3f258c..29e6fc9b9 100644 --- a/packages/vtable-sheet/package.json +++ b/packages/vtable-sheet/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable-sheet", - "version": "1.26.3", + "version": "1.26.4", "description": "Lightweight editable spreadsheet component based on VTable", "keywords": [ "vtable-sheet", @@ -50,9 +50,9 @@ "build:es": "bundle --clean -f es --ignorePostTasks" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", - "@visactor/vtable-editors": "workspace:1.26.3", - "@visactor/vtable-plugins": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", + "@visactor/vtable-editors": "workspace:1.26.4", + "@visactor/vtable-plugins": "workspace:1.26.4", "@visactor/vutils": "~1.0.17", "@visactor/vscale": "~1.0.17", "@visactor/vdataset": "~1.0.17", diff --git a/packages/vtable/CHANGELOG.json b/packages/vtable/CHANGELOG.json index 92b90ac38..9ab4e7dde 100644 --- a/packages/vtable/CHANGELOG.json +++ b/packages/vtable/CHANGELOG.json @@ -1,6 +1,18 @@ { "name": "@visactor/vtable", "entries": [ + { + "version": "1.26.4", + "tag": "@visactor/vtable_v1.26.4", + "date": "Mon, 06 Jul 2026 09:35:12 GMT", + "comments": { + "minor": [ + { + "comment": "Support rendering video cells from a first-frame canvas snapshot to release video resources after loading." + } + ] + } + }, { "version": "1.26.3", "tag": "@visactor/vtable_v1.26.3", diff --git a/packages/vtable/CHANGELOG.md b/packages/vtable/CHANGELOG.md index e66b1ad64..6efebe57c 100644 --- a/packages/vtable/CHANGELOG.md +++ b/packages/vtable/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @visactor/vtable -This log was last generated on Thu, 25 Jun 2026 03:15:59 GMT and should not be manually modified. +This log was last generated on Mon, 06 Jul 2026 09:35:12 GMT and should not be manually modified. + +## 1.26.4 +Mon, 06 Jul 2026 09:35:12 GMT + +### Minor changes + +- Support rendering video cells from a first-frame canvas snapshot to release video resources after loading. ## 1.26.3 Thu, 25 Jun 2026 03:15:59 GMT diff --git a/packages/vtable/package.json b/packages/vtable/package.json index d722b5d29..86dcb6166 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vtable", - "version": "1.26.3", + "version": "1.26.4", "description": "canvas table width high performance", "keywords": [ "grid", @@ -52,7 +52,7 @@ "fix-memory-limit": "cross-env LIMIT=10240 increase-memory-limit" }, "dependencies": { - "@visactor/vtable-editors": "workspace:1.26.3", + "@visactor/vtable-editors": "workspace:1.26.4", "@visactor/vrender-core": "~1.0.41", "@visactor/vrender-kits": "~1.0.41", "@visactor/vrender-components": "~1.0.41", diff --git a/packages/vue-vtable/package.json b/packages/vue-vtable/package.json index 078f9ceb2..1d4140aea 100644 --- a/packages/vue-vtable/package.json +++ b/packages/vue-vtable/package.json @@ -1,6 +1,6 @@ { "name": "@visactor/vue-vtable", - "version": "1.26.3", + "version": "1.26.4", "description": "The vue version of VTable", "keywords": [ "vue", @@ -59,7 +59,7 @@ "access": "public" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", + "@visactor/vtable": "workspace:1.26.4", "@visactor/vutils": "~1.0.17" }, "devDependencies": { diff --git a/tools/bugserver-trigger/package.json b/tools/bugserver-trigger/package.json index ce0b0b207..5212375b0 100644 --- a/tools/bugserver-trigger/package.json +++ b/tools/bugserver-trigger/package.json @@ -8,11 +8,11 @@ "ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts" }, "dependencies": { - "@visactor/vtable": "workspace:1.26.3", - "@visactor/vtable-gantt": "workspace:1.26.3", - "@visactor/vtable-editors": "workspace:1.26.3", - "@visactor/vtable-plugins": "workspace:1.26.3", - "@visactor/vtable-sheet": "workspace:1.26.3" + "@visactor/vtable": "workspace:1.26.4", + "@visactor/vtable-gantt": "workspace:1.26.4", + "@visactor/vtable-editors": "workspace:1.26.4", + "@visactor/vtable-plugins": "workspace:1.26.4", + "@visactor/vtable-sheet": "workspace:1.26.4" }, "devDependencies": { "@rushstack/eslint-patch": "~1.1.4", From b52813bcfd6a0f3c6b72d6bd5ba733d74a49ce76 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Wed, 8 Jul 2026 11:07:15 +0800 Subject: [PATCH 8/8] docs: update zh release note for 1.26.4 --- docs/assets/changelog/zh/release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assets/changelog/zh/release.md b/docs/assets/changelog/zh/release.md index 5cebb005a..82bb4fbec 100644 --- a/docs/assets/changelog/zh/release.md +++ b/docs/assets/changelog/zh/release.md @@ -4,7 +4,7 @@ **🆕 新增功能** -- **@visactor/vtable**: 支持 rendering video cells from a first-frame canvas snapshot release video resources after loading. +- **@visactor/vtable**: 支持通过首帧 canvas 快照渲染视频单元格,并在加载后释放视频资源。 **Full Changelog**: https://github.com/VisActor/VTable/compare/v1.26.3...v1.26.4