From 201e27040121a469a060ec9667a9b2fae9a82890 Mon Sep 17 00:00:00 2001 From: "Asm.Def" Date: Tue, 14 Mar 2023 05:17:24 +0800 Subject: [PATCH] to:feat:reminder to update --- app/src/components/options.tsx | 248 ++++++++++++++++++++++++++++++-- app/src/index.tsx | 44 ++++-- app/src/interfaces/index.ts | 3 +- graphic-walker | 2 +- pygwalker/templates/walk.js | 2 + pygwalker/utils/check_update.py | 4 +- pygwalker/utils/render.py | 3 + 7 files changed, 283 insertions(+), 23 deletions(-) diff --git a/app/src/components/options.tsx b/app/src/components/options.tsx index 4c9577bb..4bdbb44e 100644 --- a/app/src/components/options.tsx +++ b/app/src/components/options.tsx @@ -1,26 +1,254 @@ import React, { useEffect, useMemo, useState } from 'react' -import { IGWProps } from '@kanaries/graphic-walker/dist/App' -import { IAppProps } from '../interfaces'; -const url = "https://5agko11g7e.execute-api.us-west-1.amazonaws.com/default/check_updates?pkg=pygwalker-app"; +import type { IGWProps } from '../../../graphic-walker/packages/graphic-walker/dist/App' +import type { IAppProps } from '../interfaces'; -const appMeta = {}; +const copyToClipboard = async (text: string) => { + return navigator.clipboard.writeText(text); +}; +interface ISolutionProps { + header: string; + cmd: string; +} + +const updateSolutions: ISolutionProps[] = [ + { + header: 'Using pip:', + cmd: 'pip install pygwalker --upgrade', + }, + { + header: 'Using anaconda:', + cmd: 'conda install -c conda-forge pygwalker', + }, +]; + +const Solution: React.FC = (props) => { + const [busy, setBusy] = useState(false); + + return ( + <> +
+ {props.header} +
+
+ + {props.cmd} + +
{ + if (busy) { + return; + } + setBusy(true); + copyToClipboard(props.cmd).finally(() => { + setTimeout(() => { + setBusy(false); + }, 2_000); + }); + }} + > + {busy ? 'Copied' : 'Copy'} + {busy ? '\u2705' : '\u274f'} +
+
+ + ); +}; + +const HASH = (window as any)?.__GW_HASH || Math.random().toString(16).split('.').at(1); const Options: React.FC = (props: IAppProps) => { - const [outdated, setOutDated] = useState(false); + const [outdated, setOutDated] = useState(true); + const [appMeta, setAppMeta] = useState({}); + const [showUpdateHint, setShowUpdateHint] = useState(false); + const UPDATE_URL = "https://5agko11g7e.execute-api.us-west-1.amazonaws.com/default/check_updates" + const VERSION = (window as any)?.__GW_VERSION || 'current'; useEffect(() => { - const req = `${url}&v=${props.version}`; + const req = `${UPDATE_URL}?pkg=pygwalker-app&v=${VERSION}&hashcode=${HASH}&env=${process.env.NODE_ENV}`; fetch(req, { "headers": { "Content-Type": "application/json", } }).then(resp => resp.json()).then((res) => { - appMeta['data'] = res?.data; - setOutDated(res?.data?.outdated || false); - }) + setAppMeta({'data': res.data}); + setOutDated(VERSION === 'current' || res?.data?.outdated || false); + }); }, []); + useEffect(() => { + setShowUpdateHint(false); + }, [outdated]); + + useEffect(() => { + if (!showUpdateHint) { + return; + } + const handleDismiss = () => { + setShowUpdateHint(false); + }; + document.addEventListener('click', handleDismiss); + return () => { + document.removeEventListener('click', handleDismiss); + }; + }, [showUpdateHint]); + + useEffect(() => { + setShowUpdateHint(false); + }, [outdated]); + + useEffect(() => { + if (!showUpdateHint) { + return; + } + const handleDismiss = () => { + setShowUpdateHint(false); + }; + document.addEventListener('click', handleDismiss); + return () => { + document.removeEventListener('click', handleDismiss); + }; + }, [showUpdateHint]); + return (<> - { outdated && } + + {outdated && +
e.stopPropagation()} + > +

+ + {"Update: "} + {`${VERSION}\u2191`} + {` ${appMeta?.data?.latest?.release?.version || 'latest'}`} + + | + setShowUpdateHint(s => !s)} + > + {`${showUpdateHint ? 'Hide' : ' Cmd'} \u274f`} + +

+ {showUpdateHint && ( +
+ {updateSolutions.map((sol, i) => )} +
+ )} +
} ) // const ref = props.storeRef; // return (<> diff --git a/app/src/index.tsx b/app/src/index.tsx index 90b21b3e..6360bcc1 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -1,19 +1,45 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; import { GraphicWalker } from '@kanaries/graphic-walker' -import type { IGWProps } from '@kanaries/graphic-walker/dist/App' -import type { IGlobalStore } from '@kanaries/graphic-walker/dist/store' +import type { IGWProps } from '../../graphic-walker/packages/graphic-walker/dist/App' +import type { IGlobalStore } from '../../graphic-walker/packages/graphic-walker/dist/store' // import type { IGWProps } from 'gwalker/App' import Options from './components/options'; import { IAppProps } from './interfaces'; +import type { IStoInfo } from '../../graphic-walker/packages/graphic-walker/dist/utils/save'; -const App: React.FC = (props) => { - const ref = React.useRef(null); - return ( - - - ); +/** App does not consider props.storeRef */ +const App: React.FC = (propsIn) => { + const storeRef = React.useRef(null); + const props: IAppProps = {...propsIn}; + if (!props.storeRef?.current) { + props.storeRef = storeRef; + } + + useEffect(() => { + if (props.visSpec && storeRef.current) { + // TODO: DataSet and DataSource ID + const specList = JSON.parse(props.visSpec); + storeRef?.current?.vizStore?.importStoInfo({ + dataSources: [{ + id: 'dataSource-0', + data: props.dataSource, + }], + datasets: [{ + id: 'dataset-0', + name: 'DataSet', rawFields: props.rawFields, dsId: 'dataSource-0', + }], + specList: specList, + } as IStoInfo); + } + }, []); + return ( + + + + + ); } function GWalker(props: IAppProps, id: string) { diff --git a/app/src/interfaces/index.ts b/app/src/interfaces/index.ts index 1424f8ff..9b4d46c2 100644 --- a/app/src/interfaces/index.ts +++ b/app/src/interfaces/index.ts @@ -1,6 +1,7 @@ -import type { IGWProps } from '@kanaries/graphic-walker/dist/App' +import type { IGWProps } from '../../../graphic-walker/packages/graphic-walker/dist/App' export interface IAppProps extends IGWProps { version?: string; hashcode?: string; + visSpec?: string; } \ No newline at end of file diff --git a/graphic-walker b/graphic-walker index 039735fd..b055506f 160000 --- a/graphic-walker +++ b/graphic-walker @@ -1 +1 @@ -Subproject commit 039735fd2d18503fdd0ebc00323c2b3ddac68ca9 +Subproject commit b055506fd277504c9025ec9432a76250bd262980 diff --git a/pygwalker/templates/walk.js b/pygwalker/templates/walk.js index 916f936e..7fbe2607 100644 --- a/pygwalker/templates/walk.js +++ b/pygwalker/templates/walk.js @@ -2,5 +2,7 @@ var gw_id = "gwalker-{{ gwalker.id }}"; var props = {{ gwalker.props }}; console.log(PyGWalkerApp, props, gw_id); try{ +window.__GW_HASH=props.hashcode; +window.__GW_VERSION=props.version; PyGWalkerApp.GWalker(props, gw_id); }catch(e){ console.error(e); } \ No newline at end of file diff --git a/pygwalker/utils/check_update.py b/pygwalker/utils/check_update.py index 7824ebe2..9337c482 100644 --- a/pygwalker/utils/check_update.py +++ b/pygwalker/utils/check_update.py @@ -1,9 +1,9 @@ -import requests, inspect +import requests, inspect, json from .. import base __update_url__ = 'https://5agko11g7e.execute-api.us-west-1.amazonaws.com/default/check_updates' def check_update(): - payload = {'pkg': 'pygwalker', 'v': base.__version__, 'hashcode': base.__hash__, 'ctx': inspect.stack()} + payload = {'pkg': 'pygwalker', 'v': base.__version__, 'hashcode': base.__hash__} try: resp = requests.get(__update_url__, params=payload).json() if resp['data']['outdated'] == True: diff --git a/pygwalker/utils/render.py b/pygwalker/utils/render.py index 9540212a..4d36736b 100644 --- a/pygwalker/utils/render.py +++ b/pygwalker/utils/render.py @@ -25,6 +25,9 @@ def render_gwalker_html(gid: int, props: tp.Dict): walker_template = jinja_env.get_template("walk.js") props['version'] = base.__version__ props['hashcode'] = base.__hash__ + if 'spec' in props: + props['visSpec'] = props.get('spec', None) + del props['spec'] js = walker_template.render(gwalker={'id': gid, 'props': json.dumps(props, cls=DataFrameEncoder)} ) js = "var exports={};" + gwalker_script() + js