on main help sign in
bug fixes and improvements
commitee2f1d7b9662beb40357d56356dc72a9a93a1ff2 authorJohn Hyde <johnhydefour@gmail.com> dateMon 16 May 2022, 23:38 (4 years ago) parent65eba24
22 files changed+235−91
README.md+3−2 desk/desk.docket-0+1−1 desk/doc.clue−33 desk/doc.toc+13 desk/doc/overview.udon+24 desk/doc/usr/astrolabe.udon+13 desk/doc/usr/galaxy.udon+2−1 docs-dev-desk/mar/toc.hoon+16 ui/src/App.svelte+2−2 ui/src/components/EthAddressLink.svelte+9−2 ui/src/components/HomeScreenTiles.svelte+33 ui/src/components/SearchBar.svelte+10−1 ui/src/components/ShipListing.svelte+1−1 ui/src/components/ShipView.svelte+39−37 ui/src/components/TooltipAndDocLink.svelte+1−1 ui/src/components/ValidationProblems.svelte+3−1 ui/src/components/docs/Doc.svelte+1−1 ui/src/components/docs/DocHome.svelte+3 ui/src/lib/link.ts+4−2 ui/src/lib/utils.ts+17 ui/src/routes/ShipViewPage.svelte+14−6 ui/src/styles/tailwind-output.css+26
README.md+3−2
@@ -33,7 +33,8 @@ Regardless of what you run to develop, Vite will hot-reload code changes as you
3333 2. `|mount %work` to mount it.
34343. Install the desk
3535 1. From the root of the project: `./install.sh path/to/pier/astrolabe`
36− 2. From the ship's dojo: `|commit %astrolabe`
36+ 2. From the distribution ship's dojo: `:treaty|unpublish %astrolabe`
37+ 2. From middleman or distribution ship's dojo: `|commit %astrolabe`
3738 3. (if using a comet as a middleman) `|public %astrolabe`
3839 4. (if using a comet as a middleman) from the actual distribution ship: `|install ~comet-name %astrolabe`
3940 5. (if using a comet as a middleman) from the actual distribution ship: `|pause %astrolabe`
@@ -57,7 +58,7 @@ To deploy, run `npm run build` in the `ui` directory which will bundle all the c
575810. Once you've uploaded the glob, you should then update the corresponding entry in the docket file at `desk/desk.docket-0`. Both the full URL and the hash should be updated to match the glob we just created, on the line that looks like this:
5859
5960```hoon
60− glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
61+ glob-http+['https://bootstrap.urbit.org/glob-0v5.fdf99.nph65.qecq3.ncpjn.q13mb.glob' 0v5.fdf99.nph65.qecq3.ncpjn.q13mb]
6162```
6263
636411. This can now be safely committed and deployed.
desk/desk.docket-0+1−1
@@ -4,7 +4,7 @@
44 image+'https://raw.githubusercontent.com/johnhyde/astrolabe/main/ui/src/assets/astrolabe-tile.svg'
55 glob-ames+[~dister-midlev-mindyr 0v0]
66 base+'astrolabe'
7− version+[0 0 1]
7+ version+[0 0 2]
88 website+'https://github.com/johnhyde/astrolabe'
99 license+'MIT'
1010==
desk/doc.cluedeleted−33
@@ -1,33 +0,0 @@
1−:-
2−:: User docs
3−::
4−:~ [/urbit-id/udon 'What is an Urbit ID?']
5− [/galaxy/udon 'Galaxies']
6− [/star/udon 'Stars']
7− [/planet/udon 'Planets']
8− [/moon/udon 'Moons']
9− [/comet/udon 'Comets']
10− [/patpee/udon 'What is a @p?']
11− [/phonemes/udon 'Phonemes - Prefixes & Suffixes']
12− [/azimuth/udon 'What is Azimuth?']
13− [/l2/udon 'Layer 1 and Layer 2']
14− [/keys/udon 'Networking Keys']
15− :: [/baz/udon 'baz docs']
16−==
17−:: Developer docs
18−::
19−:: :~ :- %$
20−:: :~ [/foo/udon 'Some whole-desk docs']
21−:: [/bar/udon 'More whole-desk docs']
22−:: ==
23−:: :- %my-agent
24−:: :~ [/blah/udon 'blah blah']
25−:: [/etc/udon 'another doc']
26−:: ==
27−:: :- %another-agent
28−:: :~ [/foo/udon 'Foo title']
29−:: [/bar/udon 'Bar title']
30−:: [/baz/udon 'Baz title']
31−:: ==
32−:: ==
33−~
desk/doc.tocnew file+13
@@ -0,0 +1,13 @@
1+/overview/udon About Astrolabe
2+/usr User
3+ /urbit-id/udon What is an Urbit ID?
4+ /galaxy/udon Galaxies
5+ /star/udon Stars
6+ /planet/udon Planets
7+ /moon/udon Moons
8+ /comet/udon Comets
9+ /patpee/udon What is a @p?
10+ /phonemes/udon Phonemes - Prefixes & Suffixes
11+ /azimuth/udon What is Azimuth?
12+ /l2/udon Layer 1 and Layer 2
13+ /keys/udon Networking Keys
desk/doc/overview.udonnew file+24
@@ -0,0 +1,24 @@
1+;>
2+# Astrolabe
3+
4+Currently Astrolabe lets you type in `@p`s or Azimuth point numbers
5+to look up ships on the network. There's also Help Documentation if
6+you want to learn more about how Urbit IDs work.
7+
8+## Info
9+
10+- Developer: ~midlev-mindyr
11+- License: MIT
12+- App link: [~dister-midlev-mindyr/astrolabe](web+urbitgraph://~dister-midlev-mindyr/astrolabe)
13+- Source repo: [https://github.com/johnhyde/astrolabe](https://github.com/johnhyde/astrolabe)
14+- Bug reports: [https://github.com/johnhyde/astrolabe/issues](https://github.com/johnhyde/astrolabe/issues)
15+
16+## Feedback
17+
18+If you run into issues with the app, or want to voice comments or concerns,
19+please post in the group `~poster-hoster-midlev-mindyr/astrolabe`, send a DM to `~midlev-mindyr`,
20+or [create an Issue on GitHub](https://github.com/johnhyde/astrolabe/issues).
21+
22+If you see something you want to improve, please fork the [GitHub repository](https://github.com/johnhyde/astrolabe) and make a pull request.
23+
24+Thanks for using Astrolabe. I hope it serves you well.
desk/doc/usr/astrolabe.udon+13
@@ -1,5 +1,18 @@
1+;>
12# Astrolabe
23
4+Currently Astrolabe lets you type in `@p`s or Azimuth point numbers
5+to look up ships on the network. There's also Help Documentation if
6+you want to learn more about how Urbit IDs work.
7+
8+## Info
9+
10+- Developer: ~midlev-mindyr
11+- License: MIT
12+- App link: [~dister-midlev-mindyr/astrolabe](web+urbitgraph://~dister-midlev-mindyr/astrolabe)
13+- Source repo: [https://github.com/johnhyde/astrolabe](https://github.com/johnhyde/astrolabe)
14+- Bug reports: [https://github.com/johnhyde/astrolabe/issues](https://github.com/johnhyde/astrolabe/issues)
15+
316## Feedback
417
518If you run into issues with the app, or want to voice comments or concerns,
desk/doc/usr/galaxy.udon+2−1
@@ -28,8 +28,9 @@ where they can find their sponsees so that they can communicate directly withthe
2828asking a DNS server to tell you where to look for google.com.
2929Thus, it is crucially important that galaxies be online and reachable.
3030
31−## [The Galactic Senate](https://urbit.org/docs/glossary/senate)
31+## The Galactic Senate
3232
33+All galaxy owners are members of the [Galactic Senate](https://urbit.org/docs/glossary/senate).
3334Galaxy owners can [vote](https://urbit.org/docs/glossary/voting) on proposals
3435to upgrade the logic of [Azimuth](/~/scry/docs/usr/astrolabe/azimuth.html)
3536or approve the content of a document.
docs-dev-desk/mar/toc.hoonnew file+16
@@ -0,0 +1,16 @@
1+/- docs
2+|_ tab=@t
3+++ grab
4+ |%
5+ ++ mime |=([=mite len=@ud tex=@] `@t`tex)
6+ ++ noun @t
7+ ++ txt of-wain:format
8+ --
9+++ grow
10+ |%
11+ ++ mime [/text/x-toc (as-octs:mimes:html tab)]
12+ ++ noun tab
13+ ++ txt (to-wain:format tab)
14+ --
15+++ grad %txt
16+--
ui/src/App.svelte+2−2
@@ -9,9 +9,9 @@
99 const routes = {
1010 // '/': Home,
1111 '/help/:path?': HelpPage,
12− '/:patp?': ShipViewPage,
12+ // '/:patp?': ShipViewPage,
1313 '/ship/:patp?': ShipViewPage,
14− '*': Home,
14+ '*': ShipViewPage,
1515 }
1616</script>
1717
ui/src/components/EthAddressLink.svelte+9−2
@@ -2,8 +2,13 @@
22 import CopyToClipboard from "svelte-copy-to-clipboard";
33 import tooltip from "../actions/tooltip";
44
5+ let overrideText = null;
6+
57 const handleSuccessfullyCopied = (e) => {
6− // alert(`successfully copied to clipboard! ${e}`);
8+ overrideText = 'Copied!';
9+ setTimeout(() => {
10+ overrideText = null;
11+ }, 2000);
712 }
813
914 const handleFailedCopy = () => {
@@ -17,7 +22,9 @@
1722
1823{#if address}
1924 <span use:tooltip={address}>
20− <a href={`https://etherscan.io/address/${address}`} target="_blank">{abbrevAddress}</a>
25+ <a href={`https://etherscan.io/address/${address}`} target="_blank">
26+ {overrideText || abbrevAddress}
27+ </a>
2128
2229 <CopyToClipboard text={address} on:copy={handleSuccessfullyCopied} on:fail={handleFailedCopy} let:copy>
2330 <button on:click={copy} class="ml-1 text-3xl align-sub leading-4">⎘</button>
ui/src/components/HomeScreenTiles.sveltenew file+33
@@ -0,0 +1,33 @@
1+<script lang="ts">
2+ import { createEventDispatcher } from 'svelte';
3+ import DocLink from './docs/DocLink.svelte';
4+
5+ const dispatch = createEventDispatcher();
6+ function navigate(path) {
7+ dispatch('navigate', path);
8+ }
9+
10+ function convertLinks(html: string): string {
11+ let converted = html;
12+ const externalLinkRegex = /href="http/g;
13+ converted = converted.replace(externalLinkRegex, 'target="_blank" href="http');
14+ return converted;
15+ }
16+</script>
17+
18+<div class="pt-10 max-w-md w-full flex flex-wrap gap-5 justify-center">
19+ <div class="rounded-lg bg-white flex-grow max-w-xs">
20+ <DocLink modal path="">
21+ <div class="p-5 text-xl text-center">
22+ Help Docs
23+ </div>
24+ </DocLink>
25+ </div>
26+ <!-- <div class="rounded-lg bg-white flex-grow max-w-xs">
27+ <DocLink modal path="">
28+ <div class="p-5 text-xl text-center">
29+ Help Docs
30+ </div>
31+ </DocLink>
32+ </div> -->
33+</div>
ui/src/components/SearchBar.svelte+10−1
@@ -7,13 +7,14 @@
77 export let searchQuery: RegExp = /~der/;
88 export let analysis: SearchAnalysis = new SearchAnalysis();
99 export let search = '';
10+ let debouncedSearch = search;
1011
1112 // const placeholderText = "Enter a @p or non-negative integer. '*' is wildcard.";
1213 const placeholderText = "Enter a @p or non-negative integer";
1314
1415 $: {
1516 // analysis.search = search;
16− analysis = new SearchAnalysis(search);
17+ analysis = new SearchAnalysis(debouncedSearch);
1718 if (analysis.queryIsValid) {
1819 searchQuery = analysis.query;
1920 } else {
@@ -25,6 +26,14 @@
2526 patp = undefined;
2627 }
2728 }
29+
30+ let timer;
31+ $: {
32+ clearTimeout(timer);
33+ timer = setTimeout(() => {
34+ debouncedSearch = search;
35+ }, 300);
36+ }
2837</script>
2938
3039<!-- <div class="flex justify-center"> -->
ui/src/components/ShipListing.svelte+1−1
@@ -16,7 +16,7 @@
1616 }
1717</script>
1818
19−<div class="flex flex-wrap p-4 border-b last:border-none items-center"
19+<div class="flex flex-wrap p-4 border-b last:border-none items-center hover:bg-gray-300 cursor-pointer"
2020 on:click={handleClick}
2121 on:click
2222 >
ui/src/components/ShipView.svelte+39−37
@@ -17,10 +17,10 @@
1717
1818 let parentChain: any[];
1919 let rawPointInfoPromise: any;
20− let rawPointInfo: any = {};
20+ let rawPointInfo: any = null;
2121 let pointInfo: any = {};
2222 let rawSpawnedPointsPromise: any;
23− let rawSpawnedPoints: any = {};
23+ let rawSpawnedPoints: any = null;
2424 let spawnedPoints: string[] = [];
2525 const api: UrbitApi = new UrbitApi('');
2626 const spawnStatusOptions = {
@@ -80,31 +80,31 @@
8080 parentChain = parents;
8181 }
8282 $: {
83− rawPointInfo = {};
83+ rawPointInfo = null;
8484 if (azPoint) {
8585 rawPointInfoPromise = api.scry<any>({ app: 'astrolabe', path: `/point/${patp}` });
8686 rawPointInfoPromise.then((info) => {
8787 rawPointInfo = info;
8888 }).catch((error) => {
89− rawPointInfo.error = error;
89+ rawPointInfo = { error };
9090 });
9191 }
9292 }
9393 $: {
94− rawSpawnedPoints = {};
94+ rawSpawnedPoints = null;
9595 if (canSpawnPoints) {
9696 rawSpawnedPointsPromise = api.scry<any>({ app: 'astrolabe', path: `/spawned/${patp}` });
9797 rawSpawnedPointsPromise.then((info) => {
9898 rawSpawnedPoints = info;
9999 }).catch((error) => {
100− rawSpawnedPoints.error = error;
100+ rawSpawnedPoints = { error };
101101 });
102102 }
103103 }
104104 $: {
105105 spawnedPoints = [];
106− if (rawSpawnedPoints.spawned) {
107− spawnedPoints = rawSpawnedPoints.spawned.map(patp => normalizeId(patp));
106+ if (rawSpawnedPoints?.spawned) {
107+ spawnedPoints = rawSpawnedPoints?.spawned.map(patp => normalizeId(patp));
108108 }
109109 }
110110 $: {
@@ -117,39 +117,41 @@
117117 let keysSet = false;
118118 if (!azPoint) {
119119
120− } else if (rawPointInfo.point) {
121− const { point } = rawPointInfo;
122− const {
123− 'probable-dominion': dominion,
124− npoint
125− } = point;
126− spawnedCount = point['spa-count'];
127− life = npoint.net.keys.life;
128− rift = npoint.net.rift;
129− if (life > 0) {
130− keysSet = true;
131− }
120+ } else if (rawPointInfo) {
121+ if (rawPointInfo.point) {
122+ const { point } = rawPointInfo;
123+ const {
124+ 'probable-dominion': dominion,
125+ npoint
126+ } = point;
127+ spawnedCount = point['spa-count'];
128+ life = npoint.net.keys.life;
129+ rift = npoint.net.rift;
130+ if (life > 0) {
131+ keysSet = true;
132+ }
132133
133− pointInfo.proxies = npoint.own;
134− // const { dominion } = npoint;
135− layer = layerOptions[dominion];
134+ pointInfo.proxies = npoint.own;
135+ // const { dominion } = npoint;
136+ layer = layerOptions[dominion];
136137
137− if (pointInfo.proxies.owner.address === '0x86cd9cd0992f04231751e3761de45cecea5d1801') {
138− spawnStatus = spawnStatusOptions.locked;
139− } else if (keysSet) {
140− spawnStatus = spawnStatusOptions.spawned;
138+ if (pointInfo.proxies.owner.address === '0x86cd9cd0992f04231751e3761de45cecea5d1801') {
139+ spawnStatus = spawnStatusOptions.locked;
140+ } else if (keysSet) {
141+ spawnStatus = spawnStatusOptions.spawned;
142+ } else {
143+ spawnStatus = spawnStatusOptions.spawnedNoKeys;
144+ }
145+ const { sponsor } = npoint.net;
146+ if (sponsor.has) {
147+ pointInfo.sponsor = normalizeId(sponsor.who);
148+ }
141149 } else {
142− spawnStatus = spawnStatusOptions.spawnedNoKeys;
143− }
144− const { sponsor } = npoint.net;
145− if (sponsor.has) {
146− pointInfo.sponsor = normalizeId(sponsor.who);
150+ // no info found
151+ spawnStatus = spawnStatusOptions.unspawned;
152+ life = 0;
153+ rift = 0;
147154 }
148− } else if (rawPointInfo.error) {
149− // no info found
150− spawnStatus = spawnStatusOptions.unspawned;
151− life = 0;
152− rift = 0;
153155 } else {
154156 // request pending
155157 }
ui/src/components/TooltipAndDocLink.svelte+1−1
@@ -1,6 +1,6 @@
11<script lang="ts">
22 import Tooltip from "./Tooltip.svelte";
3− import DocLink from './docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
3+ import DocLink from './docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something
44 export let text: string = '';
55 export let doc: string = '';
66 let className: string = '';
ui/src/components/ValidationProblems.svelte+3−1
@@ -1,4 +1,6 @@
11<script lang="ts">
2+ import DocLink from './docs/DocLink.svelte';
3+
24 export let problems: Array<string> = [];
35 export let type: string = undefined;
46</script>
@@ -15,7 +17,7 @@
1517 {/each}
1618 <p>
1719 <!-- TODO: add link to doc -->
18− You can read more here.
20+ You can <DocLink modal path="patpee">read more here.</DocLink>
1921 </p>
2022</div>
2123
ui/src/components/docs/Doc.svelte+1−1
@@ -19,7 +19,7 @@
1919 }
2020</script>
2121
22−<div class=" rounded-lg bg-white">
22+<div class="rounded-lg bg-white">
2323 <div class="flex p-4 border-navy">
2424 {#if !homePage}
2525 <Hamburger on:click={() => navigate('')} />
ui/src/components/docs/DocHome.svelte+3
@@ -67,4 +67,7 @@
6767 </DocLink>
6868 </li>
6969 </ul>
70+ <DocLink path="astrolabe" on:navigate>
71+ <h2>About Astrolabe</h2>
72+ </DocLink>
7073</div>
ui/src/lib/link.ts+4−2
@@ -1,3 +1,5 @@
1−export function linkToShip(patp: string): string {
2− return `/ship/${patp}`;
1+import { buildQuerystring } from './utils';
2+
3+export function linkToShip(patp: string, queryParams = {}): string {
4+ return `/ship/${patp}` + buildQuerystring(queryParams);
35}
ui/src/lib/utils.ts+17
@@ -13,8 +13,25 @@ function filterObject(obj, predicate) {
1313 return Object.fromEntries(Object.entries(obj).filter(([key, value]) => predicate(key, value)));
1414}
1515
16+function buildQuerystring(data) {
17+ if (typeof (data) === 'string') return data;
18+
19+ var query = [];
20+ for (let key in data) {
21+ if (data.hasOwnProperty(key)) {
22+ // Encode each key and value, concatenate them into a string, and push them to the array
23+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
24+ }
25+ }
26+
27+ // Join each item in the array with a `&` and return the resulting string
28+ return query.length ? '?' + query.join('&') : '';
29+
30+}
31+
1632export {
1733 isStringNaN,
1834 listStrings,
1935 filterObject,
36+ buildQuerystring,
2037};
ui/src/routes/ShipViewPage.svelte+14−6
@@ -1,17 +1,17 @@
11<script lang="ts">
2− import { link, push } from 'svelte-spa-router'
2+ import { link, push, querystring } from 'svelte-spa-router'
33 import docs from '../stores/docs';
44 import { SearchAnalysis } from '../lib/id';
55 import { linkToShip } from '../lib/link';
66 import SearchBar from "../components/SearchBar.svelte";
77 import ShipView from '../components/ShipView.svelte';
88 import ValidationProblems from '../components/ValidationProblems.svelte';
9− import DocLink from '../components/docs/DocLink.svelte';
9+ import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
1010 export let params: any = {};
11− let patp: string;
1211 let analysis: SearchAnalysis = new SearchAnalysis();
1312
14− let search: string = params.patp || '';
13+ const searchParams = new URLSearchParams($querystring);
14+ let search: string = searchParams.get('search') || params.patp || '';
1515 $: paramChanged = params.patp && Date.now();
1616 $: analysisPatpChanged = analysis.patp && Date.now();
1717 $: paramChangedMoreRecently = (paramChanged - analysisPatpChanged) > 1000;
@@ -24,21 +24,29 @@
2424 search = params.patp;
2525 } else {
2626 if (analysis.patpIsValid && analysis.search === search) {
27− push(linkToShip(analysis.patp));
27+ if (analysis.patp === search) {
28+ push(linkToShip(analysis.patp));
29+ } else {
30+ push(linkToShip(analysis.patp, { search }));
31+ }
2832 }
2933 }
3034 }
3135 // if ()
3236 }
37+
3338</script>
3439
3540<div class="m-4 flex flex-col items-center space-y-8 2xs:m-8">
41+ <!-- {@debug analysis, search, params} -->
3642 <!-- <a use:link={"/"} class="p-4 bg-white rounded-lg text-center">Go Home</a> -->
3743 <!-- <SearchBar bind:searchText={search} bind:analysis /> -->
3844 <SearchBar bind:analysis bind:search />
3945 {#if analysis.patpIsValid}
4046 <ShipView patp={analysis.patp} />
41− {:else if (search.length > 0)}
47+ {:else if (analysis.search.length > 0)}
4248 <ValidationProblems problems={analysis.patpProblems} />
49+ {:else}
50+ <HomeScreenTiles />
4351 {/if}
4452</div>
ui/src/styles/tailwind-output.css+26
@@ -488,6 +488,10 @@ Ensure the default browser behavior of the `hidden` attribute.
488488 margin-top: 0.5rem;
489489 margin-bottom: 0.5rem;
490490}
491+.my-5 {
492+ margin-top: 1.25rem;
493+ margin-bottom: 1.25rem;
494+}
491495.mt-2 {
492496 margin-top: 0.5rem;
493497}
@@ -536,6 +540,12 @@ Ensure the default browser behavior of the `hidden` attribute.
536540.-mt-0\.5 {
537541 margin-top: -0.125rem;
538542}
543+.mt-10 {
544+ margin-top: 2.5rem;
545+}
546+.mt-20 {
547+ margin-top: 5rem;
548+}
539549.block {
540550 display: block;
541551}
@@ -620,6 +630,9 @@ Ensure the default browser behavior of the `hidden` attribute.
620630.max-w-2xl {
621631 max-width: 42rem;
622632}
633+.max-w-xs {
634+ max-width: 20rem;
635+}
623636.flex-shrink-0 {
624637 flex-shrink: 0;
625638}
@@ -679,6 +692,9 @@ Ensure the default browser behavior of the `hidden` attribute.
679692.gap-1 {
680693 gap: 0.25rem;
681694}
695+.gap-5 {
696+ gap: 1.25rem;
697+}
682698.space-y-4 > :not([hidden]) ~ :not([hidden]) {
683699 --tw-space-y-reverse: 0;
684700 margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
@@ -911,6 +927,12 @@ Ensure the default browser behavior of the `hidden` attribute.
911927.pl-5 {
912928 padding-left: 1.25rem;
913929}
930+.pt-20 {
931+ padding-top: 5rem;
932+}
933+.pt-10 {
934+ padding-top: 2.5rem;
935+}
914936.text-center {
915937 text-align: center;
916938}
@@ -1042,6 +1064,10 @@ Ensure the default browser behavior of the `hidden` attribute.
10421064.last\:border-none:last-child {
10431065 border-style: none;
10441066}
1067+.hover\:bg-gray-300:hover {
1068+ --tw-bg-opacity: 1;
1069+ background-color: rgb(209 213 219 / var(--tw-bg-opacity));
1070+}
10451071.hover\:opacity-40:hover {
10461072 opacity: 0.4;
10471073}