diff --git a/package-lock.json b/package-lock.json index f0839e0..58b19a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@codemirror/lang-python": "^6.1.6", "@custom-react-hooks/use-on-screen": "^1.5.1", "@feathersjs/authentication-client": "^5.0.30", + "@feathersjs/errors": "^5.0.30", "@feathersjs/feathers": "^5.0.30", "@feathersjs/socketio-client": "^5.0.30", "@react-spring/web": "^9.7.4", @@ -1290,6 +1291,7 @@ "version": "5.0.30", "resolved": "https://registry.npmjs.org/@feathersjs/errors/-/errors-5.0.30.tgz", "integrity": "sha512-9ubxwNHj3XoLJlZK1TMmEa8TDR/wn681PxNwTBqWqwhb+zLfxxgjNVelK81TX6H1r0nhV+byKQ3BO/upuBW6Iw==", + "license": "MIT", "engines": { "node": ">= 12" } @@ -1958,7 +1960,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -5583,7 +5585,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "devOptional": true }, "node_modules/bytes": { "version": "3.1.2", @@ -12628,7 +12630,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -12638,7 +12640,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -12874,7 +12876,7 @@ "version": "5.32.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.32.0.tgz", "integrity": "sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==", - "dev": true, + "devOptional": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -12926,7 +12928,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "devOptional": true }, "node_modules/tiny-invariant": { "version": "1.3.3", diff --git a/package.json b/package.json index b34d46b..18334ee 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@codemirror/lang-python": "^6.1.6", "@custom-react-hooks/use-on-screen": "^1.5.1", "@feathersjs/authentication-client": "^5.0.30", + "@feathersjs/errors": "^5.0.30", "@feathersjs/feathers": "^5.0.30", "@feathersjs/socketio-client": "^5.0.30", "@react-spring/web": "^9.7.4", diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 1071f84..be3d20a 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,5 +1,12 @@ +import { + BadRequest, + NotAuthenticated, + type FeathersError, +} from "@feathersjs/errors" import React, { useRef } from "react" import { Form } from "react-bootstrap" +import { useBrowserStore } from "../store" +import { BrowserViewRegister } from "../constants" export interface LoginFormPayload { email: string @@ -9,9 +16,15 @@ export interface LoginFormPayload { export interface LoginFormProps { className?: string onSubmit: (payload: LoginFormPayload) => void + error?: FeathersError | null } -const LoginForm: React.FC = ({ className, onSubmit }) => { +const LoginForm: React.FC = ({ + className, + onSubmit, + error, +}) => { + const setView = useBrowserStore((state) => state.setView) const formPayload = useRef({ email: "", password: "" }) const handleOnSubmit = (e: React.FormEvent) => { e.preventDefault() @@ -19,27 +32,80 @@ const LoginForm: React.FC = ({ className, onSubmit }) => { onSubmit(formPayload.current) } + console.info("[LoginForm] @render", { error }) + let errorMessages: { key: string; message: string }[] = [] + + if (error instanceof BadRequest && error.data) { + errorMessages = Object.keys(error.data).map((key) => { + return { key, message: error.data[key].message } + }) + } else if (error instanceof NotAuthenticated) { + errorMessages = [{ key: "Error", message: error.message }] + } else if (error) { + errorMessages = [{ key: "Error", message: error.message }] + } return ( -
- - Email address - (formPayload.current.email = e.target.value)} - type="email" - placeholder="name@example.com" - /> - - - Password - (formPayload.current.password = e.target.value)} - type="password" - /> - - -
+ <> +
+ {errorMessages.length > 0 ? ( +
+
    + {errorMessages.map((d, _i) => ( +
  • + {d.key}:  + {d.message} +
  • + ))} +
+
+ ) : null} + + Email address + (formPayload.current.email = e.target.value)} + type="email" + placeholder="name@example.com" + /> + + + Password + (formPayload.current.password = e.target.value)} + type="password" + /> + +
+ +
+
+ {/* Did you forget your password? */} +

+ Did you forget your password?{" "} + + Reset your password + +

+

+ Don't have an account? + +

+ +

+ Any Questions?
+ Contact us at{" "} + info@impresso-project.ch +

+ ) } diff --git a/src/components/LoginModal.tsx b/src/components/LoginModal.tsx index 3d59439..c793abd 100644 --- a/src/components/LoginModal.tsx +++ b/src/components/LoginModal.tsx @@ -1,15 +1,18 @@ import { Modal } from "react-bootstrap" +import { FeathersError } from "@feathersjs/errors" import { useBrowserStore, usePersistentStore } from "../store" import { BrowserViewLogin } from "../constants" import LoginForm, { type LoginFormPayload } from "./LoginForm" import { loginService } from "../services" +import { useEffect, useState } from "react" const LoginModal = () => { const view = useBrowserStore((state) => state.view) const setView = useBrowserStore((state) => state.setView) const setAuthenticatedUser = usePersistentStore( - (state) => state.setAuthenticatedUser + (state) => state.setAuthenticatedUser, ) + const [error, setError] = useState(null) const checkCredentials = (credentials: LoginFormPayload) => { loginService @@ -22,11 +25,15 @@ const LoginModal = () => { setAuthenticatedUser(data.user, data.accessToken) setView(null) }) - .catch((err) => { + .catch((err: FeathersError) => { + setError(err) console.error("loginService.create", err) }) } + useEffect(() => { + setError(null) + }, [view]) return ( {

Log in to your account

- +
) diff --git a/src/components/MarkdownSnippet.css b/src/components/MarkdownSnippet.css new file mode 100644 index 0000000..e95512a --- /dev/null +++ b/src/components/MarkdownSnippet.css @@ -0,0 +1,6 @@ +.MarkdownSnippet img { + max-width: 100%; + height: auto; + display: block; + margin: 0 auto; +} diff --git a/src/components/MarkdownSnippet.tsx b/src/components/MarkdownSnippet.tsx index dd0dd5f..afa8722 100644 --- a/src/components/MarkdownSnippet.tsx +++ b/src/components/MarkdownSnippet.tsx @@ -1,4 +1,5 @@ import { marked } from "marked" +import "./MarkdownSnippet.css" export interface MarkdownSnippetProps { value?: string @@ -12,7 +13,7 @@ const MarkdownSnippet: React.FC = ({ const content = marked.parse(value) return (
) diff --git a/src/content/notebooks/impresso-py-connect.mdx b/src/content/notebooks/impresso-py-connect.mdx index d2280e3..fe4d9b8 100644 --- a/src/content/notebooks/impresso-py-connect.mdx +++ b/src/content/notebooks/impresso-py-connect.mdx @@ -7,40 +7,36 @@ tags: binderUrl: https://mybinder.org/v2/gh/binder-examples/r/master?urlpath=rstudio authors: - impresso-team -date: 2024-10-16T11:20:14Z +date: 2024-10-18T06:57:36Z seealso: - impresso-py-search -sha: 677cd139a86d3264d08ef8b0b22d48f7b204fc59 +sha: 7616c20dbbf5c700ec3e7514216e9a507bf9ab65 googleColabUrl: https://colab.research.google.com/github/impresso/impresso-datalab-notebooks/blob/main/1-starter/ST_01_basics.ipynb --- {/* cell:0 cell_type:markdown */} - ## Good to know before starting This notebook is a quick introduction to the Impresso python library. It is a good starting point to understand how to interact with the library and how to use it to access the Impresso dataset. An Impresso account is required to access the data. If you don't have an account, you can register on the [Impresso Datalab page](https://impresso-project.ch/datalab/). -{/* cell:1 cell_type:markdown */} +{/* cell:1 cell_type:markdown */} ## Prerequisites Install the `impresso` python library: {/* cell:2 cell_type:code */} - ```python %pip install -q impresso ``` {/* cell:3 cell_type:markdown */} +## Initialize Impresso Client -# Initialize Impresso Client - -In this cell, we initialize the Impresso client and authenticate it with the Impresso API. +In this cell, we initialize the Impresso client and authenticate it with the Impresso API. The `impresso` object allows us to interact with the API and perform various operations such as searching for articles, retrieving entities, and fetching facets. The following call will prompt you to enter your Impresso token if it has not been authenticated within the last few hours (the token has a short lifetime). {/* cell:4 cell_type:code */} - ```python from impresso import connect @@ -48,13 +44,11 @@ impresso = connect() ``` {/* cell:5 cell_type:markdown */} - ## Making first request Let's start by making a simple request to the Impresso API. We will search for articles that contain the word "Titanic" in the text and order the results by date in an ascending order. {/* cell:6 cell_type:code */} - ```python result = impresso.search.find( q="Titanic", @@ -67,21 +61,18 @@ result The result of the search query is rendered as a notebook friendly preview when running in a Jupyter notebook. The preview contains the total number of results, the number of results returned in the current page, a link back to the Impresso App and a table with the preview of the data as a Pandas DataFrame (only the first 3 items are returned in the preview). The result object has several properties that return the data in various formats: - -- `df` returns the result as a Pandas DataFrame -- `raw` returns the result as a python list of dicts (the raw JSON response from the API) -- `data` returns the result as a list of Pydantic objects + * `df` returns the result as a Pandas DataFrame + * `raw` returns the result as a python list of dicts (the raw JSON response from the API) + * `data` returns the result as a list of Pydantic objects Explore the results as a pandas DataFrame: {/* cell:8 cell_type:code */} - ```python result.df[:5] ``` {/* cell:9 cell_type:markdown */} - ## Documentation Methods of the Impresso library are documented in the code and use Python [type hints](https://docs.python.org/3/library/typing.html) to further help with understanding what values various arguments accept. The hints are often activated on mouse hover, pressing tab or other IDE specific shortcuts. @@ -89,66 +80,46 @@ Methods of the Impresso library are documented in the code and use Python [type {/* cell:10 cell_type:markdown */} This is what function documentation looks like in VSCode Jupyter notebook: -Function documentation in VSCode Jupyter notebook +Function documentation in VSCode Jupyter notebook {/* cell:11 cell_type:markdown */} The same in Google Colab: -Function documentation in Google Colab +Function documentation in Google Colab {/* cell:12 cell_type:markdown */} Type hints in VSCode Jupyter -Type hints in VSCode Jupyter +Type hints in VSCode Jupyter {/* cell:13 cell_type:markdown */} Type hints in Google Colab: -Type hints in Google Colab +Type hints in Google Colab -{/* cell:14 cell_type:markdown */} +{/* cell:14 cell_type:markdown */} ## Namespaces of the Impresso library The Impresso library functionality is split into several namespaces that loosely follow the pages of the Impresso App. Each namespace normally contains a `find` method to search for a list of items and/or a `get` method to retrieve a single item by ID. {/* cell:15 cell_type:markdown */} - ### Search and Article The `search.find` method has been demonstrated in the previous example. The `articles.get` method can be used to retrieve an article by its ID. {/* cell:16 cell_type:code */} - ```python result = impresso.articles.get("indeplux-1909-04-10-a-i0042") result ``` {/* cell:17 cell_type:markdown */} - ### Newspaper This namespace deals with newspapers available in the Impresso library. {/* cell:18 cell_type:code */} - ```python # get a single newspaper with the most recent publication year result = impresso.newspapers.find(limit=1, order_by="-endYear") @@ -156,33 +127,28 @@ result ``` {/* cell:19 cell_type:markdown */} - ## Entities Search and retrieve metadata of entities. {/* cell:20 cell_type:code */} - ```python result = impresso.entities.find("Titanic") result ``` {/* cell:21 cell_type:code */} - ```python result = impresso.entities.get("aida-0001-50-RMS_Titanic") result ``` {/* cell:22 cell_type:markdown */} - ### Collections Manage your own collections. {/* cell:23 cell_type:code */} - ```python result = impresso.collections.find(order_by="-size") if result.size == 0: @@ -191,7 +157,7 @@ else: print("%s collections found" % result.size) first_collection_id = result.df.index[0] print("First collection contains %i items" % result.df["countItems"].iloc[0]) - + first_collection_items = impresso.search.find(collection_id=first_collection_id) print( "Got first %i items in the collection. The first item is: %s" % \ @@ -214,13 +180,11 @@ else: ``` {/* cell:24 cell_type:markdown */} - ### Text reuse Search for text reuse. {/* cell:25 cell_type:code */} - ```python # get 5 text reuse clusters starting from the second one. # The clusters are ordered by the number of passages they contain (descending). @@ -234,7 +198,6 @@ clusters ``` {/* cell:26 cell_type:code */} - ```python # get passages of the first cluster impresso.text_reuse.passages.find( @@ -243,39 +206,34 @@ impresso.text_reuse.passages.find( ``` {/* cell:27 cell_type:markdown */} - ### Facets Some namespaces have a `facet` method that can be used to retrieve various facets for the namespace index. Below are examples of how to get various facets for the `search` and `entities` namespaces. The `facet` call accepts the same filter arguments that can be passed to the `find` method. All facets come with a preview graph that can be used to visually assess the query and adjust it if necessary. {/* cell:28 cell_type:code */} - ```python impresso.search.facet(facet="daterange", q="titanic") ``` {/* cell:29 cell_type:code */} - ```python # get the number of Text Reuse clusters with a lexical overlap between 1 and 2 for every date impresso.text_reuse.clusters.facet("daterange", lexical_overlap=(1, 2)) ``` {/* cell:30 cell_type:markdown */} - ### Tools - Named entity recognition Impresso API has its own Named entity recognition (NER) service trained on the Impresso corpus. It allows to locate and classify named entities mentioned in unstructured text into pre-defined categories such as the names of persons, organizations, locations, expressions of times, quantities, monetary values, percentages, etc. {/* cell:31 cell_type:code */} - ```python text = """ -Jean-Baptiste Nicolas Robert Schuman ( -29 June 1886 – 4 September 1963) was a Luxembourg-born French -statesman. Schuman was a Christian democratic (Popular -Republican Movement) political thinker and activist. +Jean-Baptiste Nicolas Robert Schuman ( +29 June 1886 – 4 September 1963) was a Luxembourg-born French +statesman. Schuman was a Christian democratic (Popular +Republican Movement) political thinker and activist. """ result = impresso.tools.ner( text=text diff --git a/src/content/series/entities.mdx b/src/content/series/entities.mdx index 5886ccc..fbd9d1c 100644 --- a/src/content/series/entities.mdx +++ b/src/content/series/entities.mdx @@ -1,6 +1,6 @@ --- title: Explore and Visualise your Impresso Data -excerpt: "Notebook templates offer complementary views on your Impresso personal collections and external datasets beyond the capabilities of the Impresso Web App." +excerpt: "" notebooks: - impresso-py-maps - impresso-py-network @@ -8,3 +8,5 @@ category: - explorations position: central-column --- + +Notebook templates offer complementary views on your Impresso personal collections and external datasets beyond the capabilities of the Impresso Web App. diff --git a/src/styles/global.css b/src/styles/global.css index d2871f3..3e97920 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -81,20 +81,20 @@ --font-size-h5: var(--fs18px); --font-small: var(--fs12px); - /* FONT WEIGHT */ - --font-weight-bold: 600; - /* small caps & Variable font settings*/ --impresso-font-size-smallcaps: 0.72em; --impresso-letter-spacing-smallcaps: 0.08em; --impresso-wght-normal: 450; --impresso-wght-smallcaps: 580; - --impresso-wght-bold: 700; + --impresso-wght-bold: 650; --impresso-wght-medium: 550; + --impresso-wght-bolder: 750; --impresso-wght-extrabold: 900; - --impresso-font-size-sm: var(--fs12px); + /* FONT WEIGHT */ + --font-weight-bold: var(--impresso-wght-bold); + /* EASING */ --backInOut: cubic-bezier(0.68, -0.55, 0.265, 1.55); --backOut: cubic-bezier(0.175, 0.885, 0.32, 1.275); @@ -144,32 +144,42 @@ em { } b { - font-weight: 600; + font-weight: var(--impresso-wght-bold); /* variable font weight */ - font-variation-settings: "wght" 600; + font-variation-settings: "wght" var(--impresso-wght-bold); +} + +hr { + border-bottom: 2px solid var(--impresso-color-black); + margin-bottom: 2rem; } +/* restyle bootstrap button */ +button { + display: flex !important; + align-items: center; +} + +button svg { + margin-left: -0.2rem; +} .btn { + font-weight: var(--impresso-wght-bold); + font-variation-settings: "wght" var(--impresso-wght-bold); + font-size: var(--impresso-font-size-smallcaps); text-transform: uppercase; font-variant: normal; letter-spacing: var(--impresso-letter-spacing-smallcaps); line-height: 2em; - font-weight: var(--impresso-wght-smallcaps); - font-variation-settings: "wght" var(--impresso-wght-smallcaps); + white-space: nowrap; --bs-border-radius: var(--impresso-border-radius-sm); } - -hr { - border-bottom: 2px solid var(--impresso-color-black); - margin-bottom: 2rem; -} - .btn-primary { --bs-btn-color: var(--impresso-color-black); --bs-btn-bg: var(--impresso-color-yellow); - --bs-btn-border-color: var(--impresso-color-yellow); + --bs-btn-border-color: var(--impresso-color-black); --bs-btn-hover-color: var(--impresso-color-black); --bs-btn-hover-bg: var(--impresso-color-yellow); --bs-btn-hover-border-color: var(--impresso-color-black); @@ -182,17 +192,9 @@ hr { --bs-btn-disabled-bg: var(--impresso-color-yellow); --bs-btn-disabled-border-color: var(--impresso-color-black); } -/* restyle bootstrap button */ -button { - display: flex !important; - align-items: center; -} - -button svg { - margin-left: -0.2rem; -} .btn.btn-primary { border: 2px var(--impresso-color-yellow) solid; + font-variation-settings: "wght" var(--impresso-wght-extrabold); } .btn-primary-outline { @@ -207,7 +209,9 @@ button svg { .btn-secodary.btn-sm { --bs-btn-bg: transparent; --bs-btn-color: var(--impresso-color-black); - border: 2px var(--impresso-color-black) solid !important; + border: 2px var(--impresso-color-black-rgba-50) solid !important; + font-weight: var(--impresso-wght-bolder); + font-variation-settings: "wght" var(--impresso-wght-bolder); } button.btn-secodary:hover { diff --git a/updatenotebooks.js b/updatenotebooks.js index ec5af3e..84151c9 100644 --- a/updatenotebooks.js +++ b/updatenotebooks.js @@ -57,6 +57,11 @@ if (notebookToUpdate) { } console.log("✓ sha:", commit.sha) console.log("✓ date:", commit.commit.author.date) + // if the sha is the same as the one in the frontmatter, we skip + if (frontmatter.sha === commit.sha) { + console.log("⚠ skipping, sha is the same as the one in the frontmatter") + continue + } // get the ipynb content const ipynbUrl = url .replace("github.com", "raw.githubusercontent.com")