diff --git a/.eslintrc.cjs b/.eslintrc.cjs.bak similarity index 100% rename from .eslintrc.cjs rename to .eslintrc.cjs.bak diff --git a/package.json b/package.json index 3a63fce..0c3c02a 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,8 @@ "eslint-plugin-qwik": "latest", "np": "7.6.1", "prettier": "2.8.8", + "sucrase": "^3.35.0", + "tiny-glob": "^0.2.9", "typescript": "5.0.4", "undici": "5.22.0", "vite": "4.3.5" diff --git a/src/components/Pagination.tsx b/src/components/Pagination.jsx similarity index 67% rename from src/components/Pagination.tsx rename to src/components/Pagination.jsx index 2a58abb..3f68fcb 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.jsx @@ -1,28 +1,20 @@ -import { Signal, component$, useStylesScoped$, $, useComputed$ } from '@builder.io/qwik'; +import { component$, useSignal, useStylesScoped$, $, useComputed$ } from '@builder.io/qwik'; -interface pageProps { - pageNo: Signal, - postPerPage: Signal, - totalPosts: Signal -} -export const Pagination = component$((props: pageProps) => { + + + + + +export const Pagination = component$((props) => { useStylesScoped$(AppCSS); const totalPage = useComputed$(() => { - return Math.ceil((props.totalPosts.value / props.postPerPage.value)) - 1; + return Math.ceil((props.totalPosts.value / props.postPerPage.value)) || 1; }); - const changePosts = $((e: any) => { - props.postPerPage.value = e.target.value as number; - }) - - const changePageNo = $((e: any) => { - props.pageNo.value = e.target.value as number; - }) - const decPage = $(() => { - if (props.pageNo.value !== 0) props.pageNo.value--; + if (props.pageNo.value > 1) props.pageNo.value--; }) const incPage = $(() => { @@ -30,33 +22,46 @@ export const Pagination = component$((props: pageProps) => { }) const setFirstPage = $(() => { - if(props.pageNo.value !== 0) props.pageNo.value = 0; + if(props.pageNo.value !== 1) props.pageNo.value = 1; }) const setLastPage = $(() => { if(props.pageNo.value !== totalPage.value) props.pageNo.value = totalPage.value; }) + const postPerPageLast = useSignal(props.postPerPage.value); + + const postPerPageChange = $(() => { + // scale pageNo to keep the first row in focus + const firstRowIdx = postPerPageLast.value * (props.pageNo.value - 1); + const totalPage2 = Math.ceil((props.totalPosts.value / props.postPerPage.value)) || 1; + const pageNo2 = Math.floor(Math.min(totalPage2, (firstRowIdx / props.postPerPage.value) + 1)); + //console.log(`postPerPage: ${postPerPageLast.value} -> ${props.postPerPage.value}`); + //console.log(`totalPage: ${totalPage.value} -> ${totalPage2}`); + //console.log(`firstRowIdx: ${firstRowIdx}`); + //console.log(`pageNo: ${props.pageNo.value} -> ${pageNo2}`); + props.pageNo.value = pageNo2; + postPerPageLast.value = props.postPerPage.value; + }); + return (
Rows per page
- + {props.postPerPageValues.map((postPerPage, idx) => ( + + ))}
-
Page of {totalPage.value}
+
Page of {totalPage.value}
-
); }); diff --git a/src/components/SortButton.tsx b/src/components/SortButton.jsx similarity index 71% rename from src/components/SortButton.tsx rename to src/components/SortButton.jsx index 0b084fd..04c6b71 100644 --- a/src/components/SortButton.tsx +++ b/src/components/SortButton.jsx @@ -1,12 +1,12 @@ -import { component$, useOn, useStylesScoped$, $, Signal } from '@builder.io/qwik'; +import { component$, useOn, useStylesScoped$, $, } from '@builder.io/qwik'; -interface typeProps { - cellKey: string | number | null | undefined, - sortOrder: Signal, - sortKey: Signal -} -export const SortButton = component$((props: typeProps) => { + + + + + +export const SortButton = component$((props) => { useStylesScoped$(AppCSS); useOn( 'click', diff --git a/src/components/header/Header.tsx b/src/components/header/Header.jsx similarity index 76% rename from src/components/header/Header.tsx rename to src/components/header/Header.jsx index 08d3dce..85d56b3 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.jsx @@ -1,18 +1,18 @@ -import { Signal, component$, useStylesScoped$ } from "@builder.io/qwik"; +import { component$, useStylesScoped$ } from "@builder.io/qwik"; import { Search } from '../Search'; -interface headerProps { - headers: { - key: string, - label: string - }[], - searchBy: Signal, - searchInp: Signal, - title: string, - headerImg?: string -} - -export const Header = component$((props: headerProps) => { + + + + + + + + + + + +export const Header = component$((props) => { useStylesScoped$(AppCSS); return (
diff --git a/src/components/table/QwikTable.tsx b/src/components/table/QwikTable.jsx similarity index 73% rename from src/components/table/QwikTable.tsx rename to src/components/table/QwikTable.jsx index 43c90ac..9a5db41 100644 --- a/src/components/table/QwikTable.tsx +++ b/src/components/table/QwikTable.jsx @@ -6,19 +6,19 @@ import { sortData } from '../../utils/sortData'; import { searchData } from '../../utils/searchedData'; import { Header } from '../header/Header'; -interface tableProps { - header: { - key: string, - label: string - }[], - data: { - [key: string]: string | number | null | undefined - }[], - title: string, - headerImg?: string -} - -export const QwikTable = component$((props: tableProps) => { + + + + + + + + + + + + +export const QwikTable = component$((props) => { useStyles$(` table { border-collapse: collapse; @@ -34,14 +34,33 @@ export const QwikTable = component$((props: tableProps) => { ); useStylesScoped$(AppCSS); - const sortOrder = useSignal('asc'); - const sortKey = useSignal(props.header[0].key); - const pageNo = useSignal(0); - const postPerPage = useSignal(10); - const totalPosts = useSignal(props.data.length); - const searchBy = useSignal(props.header[0].key); - const searchInp = useSignal(''); - const prevSearch = useSignal(false); + const postPerPageValues = [ + 10, + 20, + 30, + 50, + 100, + 200, + 300, + 500, + 1000, + 2000, + 3000, + 5000, + 10000, + ]; + + const postPerPageDefaultValue = 100; + + const sortOrder = useSignal('asc'); + //const sortKey = useSignal(props.header[0].key); + const sortKey = useSignal(undefined); // perf: dont sort by default + const pageNo = useSignal(1); + const postPerPage = useSignal(postPerPageDefaultValue); + const totalPosts = useSignal(props.data.length); + const searchBy = useSignal(props.header[0].key); + const searchInp = useSignal(''); + const prevSearch = useSignal(false); const finalData = useStore({ items: props.data @@ -54,6 +73,7 @@ export const QwikTable = component$((props: tableProps) => { sortOrder: sortOrder, totalPosts: totalPosts, prevSearch: prevSearch, + searchInp: searchInp, searchBy: searchBy })) @@ -95,6 +115,13 @@ export const QwikTable = component$((props: tableProps) => { headerImg={props.headerImg} /> + + { diff --git a/src/components/tableBody/tableBody.tsx b/src/components/tableBody/TableBody.jsx similarity index 61% rename from src/components/tableBody/tableBody.tsx rename to src/components/tableBody/TableBody.jsx index bef59c8..812df15 100644 --- a/src/components/tableBody/tableBody.tsx +++ b/src/components/tableBody/TableBody.jsx @@ -1,24 +1,25 @@ -import { Signal, component$, useComputed$, useStylesScoped$ } from '@builder.io/qwik'; +import { component$, useComputed$, useStylesScoped$ } from '@builder.io/qwik'; import { isImage } from '../../utils/imageBool'; -interface bodyProps { - data: { - [key: string]: string | number | null | undefined - }[], - pageNo: Signal, - postPerPage: Signal -} -type cellType = { - [key: string]: string | number | null | undefined; -} -export const TableBody = component$((props: bodyProps) => { + + + + + + + + + + +export const TableBody = component$((props) => { useStylesScoped$(AppCSS); const computedPosts = useComputed$(() => { - return props.data.slice((props.pageNo.value * props.postPerPage.value), - ((props.pageNo.value * props.postPerPage.value) + const pageIdx = props.pageNo.value - 1; + return props.data.slice((pageIdx * props.postPerPage.value), + ((pageIdx * props.postPerPage.value) + parseInt(props.postPerPage.value.toString()))) }) @@ -27,13 +28,13 @@ export const TableBody = component$((props: bodyProps) => { return ( - {computedPosts.value.map((cell: cellType) => { + {computedPosts.value.map((cell) => { const keys = Object.keys(cell); return ( {keys.map((item, i) => { if (isImage(cell[item])) { - const imgSrc = cell[item] as string; + const imgSrc = cell[item] ; return ( - {props.header.map((cell: cellType, i) => { + {props.header.map((cell, i) => { return (
diff --git a/src/components/tableHead/tableHead.tsx b/src/components/tableHead/TableHead.jsx similarity index 57% rename from src/components/tableHead/tableHead.tsx rename to src/components/tableHead/TableHead.jsx index 7cf9022..2e19698 100644 --- a/src/components/tableHead/tableHead.tsx +++ b/src/components/tableHead/TableHead.jsx @@ -1,25 +1,25 @@ -import { Signal, component$, useStylesScoped$ } from '@builder.io/qwik'; +import { component$, useStylesScoped$ } from '@builder.io/qwik'; import { SortButton } from '../SortButton'; -interface HeaderProps { - header: { - key: string, - label: string - }[], - sortOrder: Signal, - sortKey: Signal -} -type cellType = { - [key: string]: string | number | null | undefined; -} -export const TableHead = component$((props: HeaderProps) => { + + + + + + + + + + + +export const TableHead = component$((props) => { useStylesScoped$(AppCSS); return (
{cell[Object.keys(cell)[1]]} diff --git a/src/entry.dev.tsx b/src/entry.dev.jsx similarity index 81% rename from src/entry.dev.tsx rename to src/entry.dev.jsx index 4f8f58f..3ad4bdc 100644 --- a/src/entry.dev.tsx +++ b/src/entry.dev.jsx @@ -9,9 +9,9 @@ * - More code is transferred to the browser than in SSR mode. * - Optimizer/Serialization/Deserialization code is not exercised! */ -import { render, type RenderOptions } from '@builder.io/qwik'; +import { render, } from '@builder.io/qwik'; import Root from './root'; -export default function (opts: RenderOptions) { +export default function (opts) { return render(document, , opts); } diff --git a/src/entry.ssr.tsx b/src/entry.ssr.jsx similarity index 74% rename from src/entry.ssr.tsx rename to src/entry.ssr.jsx index cec868f..8e18953 100644 --- a/src/entry.ssr.tsx +++ b/src/entry.ssr.jsx @@ -10,11 +10,11 @@ * - npm run build * */ -import { renderToStream, type RenderToStreamOptions } from '@builder.io/qwik/server'; +import { renderToStream, } from '@builder.io/qwik/server'; import { manifest } from '@qwik-client-manifest'; import Root from './root'; -export default function (opts: RenderToStreamOptions) { +export default function (opts) { return renderToStream(, { manifest, ...opts, diff --git a/src/index.ts b/src/index.js similarity index 100% rename from src/index.ts rename to src/index.js diff --git a/src/root.tsx b/src/root.jsx similarity index 100% rename from src/root.tsx rename to src/root.jsx diff --git a/src/utils/imageBool.ts b/src/utils/imageBool.js similarity index 61% rename from src/utils/imageBool.ts rename to src/utils/imageBool.js index 342b3fb..18cfab8 100644 --- a/src/utils/imageBool.ts +++ b/src/utils/imageBool.js @@ -1,4 +1,4 @@ -export const isImage = (url: string | number | null | undefined) => { +export const isImage = (url) => { if (typeof (url) === 'string') return /\.(jpg|jpeg|png|webp|avif|gif|svg)$/.test(url); return false; diff --git a/src/utils/searchedData.js b/src/utils/searchedData.js new file mode 100644 index 0000000..6e90a2b --- /dev/null +++ b/src/utils/searchedData.js @@ -0,0 +1,47 @@ + + +export function searchData({ + data, + pageNo, + sortKey, + sortOrder, + totalPosts, + searchBy, + searchInp, + prevSearch +} + + + + + + + + + + +) { + if (!prevSearch.value) pageNo.value = 1; + + prevSearch.value = true; + + const searchedData = []; + data.forEach((row) => { + const val = row[searchBy.value]; + if (val === undefined || val === null) return; + if ((val.toString()).toLowerCase().indexOf(searchInp.value.toLowerCase()) === -1) { + // do nothing + } else { + searchedData.push(row) + } + }) + + const finalData = searchedData.sort((a, b) => { + return a[sortKey.value] > b[sortKey.value] ? 1 : -1; + }); + + totalPosts.value = finalData.length; + + if (sortOrder.value === 'dsc') return finalData.reverse(); + return finalData; +} \ No newline at end of file diff --git a/src/utils/searchedData.ts b/src/utils/searchedData.ts deleted file mode 100644 index dbfa1ac..0000000 --- a/src/utils/searchedData.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Signal } from "@builder.io/qwik"; - -export function searchData({ - data, - pageNo, - sortKey, - sortOrder, - totalPosts, - searchBy, - searchInp, - prevSearch -}: { - data: { - [key: string]: string | number | null | undefined - }[]; - pageNo: Signal; - sortKey: Signal; - sortOrder: Signal; - totalPosts: Signal; - searchBy: Signal; - searchInp: Signal; - prevSearch: Signal; -}) { - if (!prevSearch.value) pageNo.value = 0; - - prevSearch.value = true; - - const searchedData: any[] = []; - data.forEach((row: any) => { - if ((row[searchBy.value].toString()).toLowerCase().indexOf(searchInp.value.toLowerCase()) === -1) { - // do nothing - } else { - searchedData.push(row) - } - }) - - const finalData = searchedData.sort((a: any, b: any) => { - return a[sortKey.value] > b[sortKey.value] ? 1 : -1; - }); - - totalPosts.value = finalData.length; - - if (sortOrder.value === 'dsc') return finalData.reverse(); - return finalData; -} \ No newline at end of file diff --git a/src/utils/sortData.js b/src/utils/sortData.js new file mode 100644 index 0000000..1058f5c --- /dev/null +++ b/src/utils/sortData.js @@ -0,0 +1,48 @@ + + +export function sortData({ + data, + tableData, + sortKey, + sortOrder, + totalPosts, + prevSearch, + searchInp, + searchBy +} + + + + + + + + + + + +) { + const initialSearchKey = searchBy.value; + + if (tableData.length === 0 || searchInp.value === '') tableData = data; + + totalPosts.value = tableData.length; + + if (sortKey.value === undefined) return tableData; + + if(prevSearch.value) { + prevSearch.value = false; + tableData = data; + sortKey.value = initialSearchKey; + sortOrder.value = 'asc'; + } + const sortedData = tableData.sort((a, b) => { + return a[sortKey.value] > b[sortKey.value] ? 1 : -1; + }); + + if (sortOrder.value === 'dsc') { + return sortedData.reverse(); + } + + return sortedData; +} diff --git a/src/utils/sortData.ts b/src/utils/sortData.ts deleted file mode 100644 index b514378..0000000 --- a/src/utils/sortData.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Signal } from "@builder.io/qwik"; - -export function sortData({ - data, - tableData, - sortKey, - sortOrder, - totalPosts, - prevSearch, - searchBy -}: { - data: { - [key: string]: string | number | null | undefined - }[]; - tableData: { - [key: string]: string | number | null | undefined - }[]; - sortKey: Signal; - sortOrder: Signal; - totalPosts: Signal; - prevSearch: Signal; - searchBy: Signal; -}) { - const initialSearchKey = searchBy.value; - if (!sortKey.value) return tableData; - - if (tableData.length === 0) tableData = data; - - totalPosts.value = data.length; - if(prevSearch.value) { - prevSearch.value = false; - tableData = data; - sortKey.value = initialSearchKey; - sortOrder.value = 'asc'; - } - const sortedData = tableData.sort((a: any, b: any) => { - return a[sortKey.value] > b[sortKey.value] ? 1 : -1; - }); - - if (sortOrder.value === 'dsc') { - return sortedData.reverse(); - } - - return sortedData; -} diff --git a/tsx2jsx.mjs b/tsx2jsx.mjs new file mode 100644 index 0000000..381a156 --- /dev/null +++ b/tsx2jsx.mjs @@ -0,0 +1,125 @@ +/* +tsx2jsx.mjs +convert typescript to vanillajs (actual javascript) + +npm i -D sucrase tiny-glob +node tsx2jsx.mjs + +https://github.com/alangpierce/sucrase/issues/559 +https://github.com/milahu/random/blob/master/javascript/tsx2jsx.mjs +*/ + +import fs from 'fs'; +import {spawnSync} from 'child_process'; + +import glob from 'tiny-glob'; + +import {HelperManager} from "sucrase/dist/HelperManager.js"; +import identifyShadowedGlobalsModule from "sucrase/dist/identifyShadowedGlobals.js"; +import NameManagerModule from "sucrase/dist/NameManager.js"; +import {parse} from "sucrase/dist/parser/index.js"; +import TokenProcessorModule from "sucrase/dist/TokenProcessor.js"; +import RootTransformerModule from "sucrase/dist/transformers/RootTransformer.js"; +import getTSImportedNamesModule from "sucrase/dist/util/getTSImportedNames.js"; + +// workaround ... +// TypeError: NameManager is not a constructor +const NameManager = NameManagerModule.default; +const TokenProcessor = TokenProcessorModule.default; +const getTSImportedNames = getTSImportedNamesModule.default; +const identifyShadowedGlobals = identifyShadowedGlobalsModule.default; +const RootTransformer = RootTransformerModule.default; + +function transformTSOnly(code) { + const {tokens, scopes} = parse( + code, + true /* isJSXEnabled */, + true /* isTypeScriptEnabled */, + false /* isFlowEnabled */, + ); + const nameManager = new NameManager(code, tokens); + const helperManager = new HelperManager(nameManager); + const tokenProcessor = new TokenProcessor(code, tokens, false /* isFlowEnabled */, helperManager); + + identifyShadowedGlobals(tokenProcessor, scopes, getTSImportedNames(tokenProcessor)); + const sucraseContext = { + tokenProcessor, + scopes, + nameManager, + importProcessor: null, + helperManager, + }; + + // https://github.com/alangpierce/sucrase#transforms + const sucraseOptions = { + transforms: ["typescript"], + disableESTransforms: true, // keep modern javascript: Optional chaining, Nullish coalescing, ... + }; + + const transformer = new RootTransformer(sucraseContext, ["typescript"], false, sucraseOptions); + return transformer.transform(); +} + +const gitDirtyWithSelf = spawnSync('git', ['diff-index', 'HEAD', '--'], { encoding: 'utf8' }).stdout; +const gitDirty = gitDirtyWithSelf.replace(/(?:^|\n):[0-9]+ [0-9]+ [0-9a-f]+ \w+ M\s+tsx2jsx\.mjs(?:\n|$)/, '\n').trim(); +if (gitDirty != '') { + console.log(`error: git is dirty:\n\n${gitDirty}`); + process.exit(1); +} + +const foExtMap = { + 'ts': 'js', + 'tsx': 'jsx', +}; + +const globExt = '{' + Object.keys(foExtMap).join(',') + '}'; +// example: {ts,tsx} + +const globStr = `**/*.${globExt}`; // convert all files in workdir +//const globStr = `./src/**/*.${extList}`; // convert all files in src/ folder +console.log(`globStr = ${globStr}`) + +const todoTransform = []; + +for (const fi of await glob(globStr)) { + + //console.log(fi); continue; // debug + + const fiParts = fi.split('.'); + if (fiParts.slice(-2)[0] == 'd') { + console.log(`skip .d.ts file: ${fi}`); + continue; + } + const fiExt = fiParts.slice(-1)[0]; + const foExt = foExtMap[fiExt]; + if (!foExt) { + console.log(`FIXME handle input file extension ${JSON.stringify(fiExt)} for input file ${JSON.stringify(fi)}`); + continue; + } + const fo = fi.split('.').slice(0, -1).join('.') + '.' + foExt; + console.log(`rename: ${fi} -> ${fo}`); + spawnSync('git', ['mv', '-v', fi, fo]); // rename + todoTransform.push([fi, fo]); +} +if (todoTransform.length == 0) { + console.log(`not found any *.tsx files`); + process.exit(1); +} +spawnSync('git', ['commit', '-m', 'tsx2jsx: rename']); // commit + +for (const [fi, fo] of todoTransform) { + console.log(`transform: ${fi} -> ${fo}`); + const i = fs.readFileSync(fo, 'utf8'); + const o = transformTSOnly(i); + // always do your backups :P + fs.writeFileSync(fo, o.code, 'utf8'); // replace + spawnSync('git', ['add', fo]); // add +} +spawnSync('git', ['commit', '-m', 'tsx2jsx: transform']); // commit + +console.log(` +next steps: + +git diff HEAD^ # inspect transform +git reset --hard HEAD~2 # undo transform + rename +`); diff --git a/vite.config.ts b/vite.config.js similarity index 100% rename from vite.config.ts rename to vite.config.js