Skip to content

Commit

Permalink
to:feat:reminder to update
Browse files Browse the repository at this point in the history
  • Loading branch information
WooJson authored and ObservedObserver committed Mar 26, 2023
1 parent 2411f53 commit 201e270
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 23 deletions.
248 changes: 238 additions & 10 deletions 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<ISolutionProps> = (props) => {
const [busy, setBusy] = useState(false);

return (
<>
<header>
{props.header}
</header>
<div>
<code>
{props.cmd}
</code>
<div
role="button"
aria-label="Copy command"
tabIndex={0}
aria-disabled={busy}
onClick={() => {
if (busy) {
return;
}
setBusy(true);
copyToClipboard(props.cmd).finally(() => {
setTimeout(() => {
setBusy(false);
}, 2_000);
});
}}
>
<small>{busy ? 'Copied' : 'Copy'}</small>
<span>{busy ? '\u2705' : '\u274f'}</span>
</div>
</div>
</>
);
};

const HASH = (window as any)?.__GW_HASH || Math.random().toString(16).split('.').at(1);
const Options: React.FC<IAppProps> = (props: IAppProps) => {
const [outdated, setOutDated] = useState<Boolean>(false);
const [outdated, setOutDated] = useState<Boolean>(true);
const [appMeta, setAppMeta] = useState<any>({});
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 && <button>Get Latest Release</button>}
<style>{`
.update_link {
position: fixed;
right: 2rem;
top: 2rem;
background-color: rgba(56,189,248,.1);
color: rgb(2, 132, 199);
margin-top: 50px;
--line-height: 1.25rem;
line-height: var(--line-height);
--padding-block: 0.25rem;
padding: var(--padding-block) 0.75rem;
border-radius: calc(var(--padding-block) + var(--line-height) * .5);
font-weight: 500;
font-size: .75rem;
font-family: sans-serif;
text-align: end;
}
.update_link p {
margin: 0;
padding: 0;
}
.update_link *[role="button"] {
cursor: pointer;
}
.update_link *[role="separator"] {
user-select: none;
margin-inline: 0.5em;
display: inline-block;
}
.update_link .solutions {
margin: 1em 0 1em 2em;
display: grid;
grid-template-columns: repeat(2, auto);
gap: 0.2em 0.8em;
font-family: monospace;
}
.update_link .solutions > * {
display: flex;
align-items: center;
}
.update_link .solutions div:has(> code) {
display: flex;
align-items: center;
background-color: #777;
color: #eee;
padding-inline: 0.8em 6em;
padding-block: 0.1em;
border-radius: 2px;
position: relative;
height: 2em;
}
.update_link *[aria-disabled="true"] {
opacity: 1 !important;
cursor: default !important;
}
.update_link .solutions div *[role="button"] {
user-select: none;
flex-grow: 0;
flex-shrink: 0;
cursor: pointer;
margin-left: 1em;
background-color: #eee;
color: #444;
padding-inline: 0.4em;
font-size: 0.9rem;
display: flex;
align-items: center;
border-radius: 1em;
opacity: 0.5;
position: absolute;
right: 0.5em;
z-index: 10;
font-family:
}
.update_link .solutions div:hover *[role="button"] {
opacity: 1;
}
.update_link .solutions div *[role="button"] > small {
font-size: 0.65rem;
margin-right: 0.5em;
}
.update_link .solutions div > code {
flex-grow: 1;
flex-shrink: 1;
text-align: start;
}
.update_link a {
font-family: monospace;
color: inherit;
text-decoration: none;
cursor: pointer;
}
.update_link a span {
filter: brightness(1.8);
}
.update_link p span {
font-family: monospace;
color: inherit;
}
@media (prefers-color-scheme: dark) {
.update_link span, .update_link header, .update_link a {
filter: brightness(1.5);
}
}
`}</style>
{outdated &&
<div
className="update_link"
aria-live="assertive"
role="alert"
tabIndex={0}
onClick={e => e.stopPropagation()}
>
<p>
<a href="https://pypi.org/project/pygwalker" target="_blank">
{"Update: "}
{`${VERSION}\u2191`}
<span>{` ${appMeta?.data?.latest?.release?.version || 'latest'}`}</span>
</a>
<span role="separator">|</span>
<span
aria-haspopup
role="button"
tabIndex={0}
onClick={() => setShowUpdateHint(s => !s)}
>
{`${showUpdateHint ? 'Hide' : ' Cmd'} \u274f`}
</span>
</p>
{showUpdateHint && (
<div className="solutions">
{updateSolutions.map((sol, i) => <Solution key={i} {...sol} />)}
</div>
)}
</div> }
</>)
// const ref = props.storeRef;
// return (<>
Expand Down
44 changes: 35 additions & 9 deletions 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<IAppProps> = (props) => {
const ref = React.useRef<IGlobalStore | null>(null);
return (<React.StrictMode>
<Options {...props} storeRef={ref} />
<GraphicWalker {...props} storeRef={ref} />
</React.StrictMode>);
/** App does not consider props.storeRef */
const App: React.FC<IAppProps> = (propsIn) => {
const storeRef = React.useRef<IGlobalStore|null>(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 (
<React.StrictMode>
<GraphicWalker {...props} />
<Options {...props} />
</React.StrictMode>
);
}

function GWalker(props: IAppProps, id: string) {
Expand Down
3 changes: 2 additions & 1 deletion 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;
}
2 changes: 2 additions & 0 deletions pygwalker/templates/walk.js
Expand Up @@ -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); }
4 changes: 2 additions & 2 deletions 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:
Expand Down
3 changes: 3 additions & 0 deletions pygwalker/utils/render.py
Expand Up @@ -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

Expand Down

0 comments on commit 201e270

Please sign in to comment.