Compare commits

..

30 Commits

Author SHA1 Message Date
0fed104be1 Merge branch 'master' into 3d-world 2023-05-20 00:46:30 -04:00
2feea0d645 remove console.log 2023-05-19 08:11:34 -06:00
8f3a11bb98 in stats-world, fix the location computations to be based on Give attributes 2023-05-18 20:34:57 -06:00
beb7821f58 in stats-world, refactor claim-loading code to separate file (no logic changes) 2023-05-18 18:54:11 -06:00
712b25bc71 add error message to stats page 2023-05-18 18:23:17 -06:00
f039f98b61 on stats-world: adjust some lighting and position, and add an attempt at a screen-capture 2023-05-18 18:04:10 -06:00
f7a149444a tweak timing & spacing of stats-world, and log relevant tasks 2023-05-17 21:13:01 -06:00
58e962a3bd load stats-world bushes and make 'em grow! 2023-05-17 20:46:40 -06:00
7160aa3cc5 remove test cube from stats-world 2023-05-17 18:22:46 -06:00
786f0bd94a make the lights wait to turn on in stats-world 2023-05-17 18:20:55 -06:00
b5db2b4140 refactor variable names (no logic changes) 2023-05-17 17:42:26 -06:00
faa7959929 make all lights move in stats-world 2023-05-17 17:40:10 -06:00
3dd1b6f6f0 add some other screen actions (kinda ugly); add load of claims 2023-05-17 08:46:10 -06:00
30b8b941ae Merge pull request 'copy message & infinite scroll start' (#17) from hack-copy into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#17
2023-05-17 02:32:12 -04:00
9b1c51ba15 adjust stats-world perspective & size & colors 2023-05-16 18:56:50 -06:00
8c6c32ed20 finish removal from last commit 2023-05-16 16:40:55 -06:00
0eaf72b83b remove yet more randomness that has no effect in stats-world 2023-05-16 16:30:33 -06:00
41d3ad56f5 remove some random numbers that don't seem to make a difference in stats-world 2023-05-16 16:26:27 -06:00
0227d32f15 add a simple box in stats-world 2023-05-16 16:25:32 -06:00
b5ab485354 enable zooming on the stats-world 2023-05-16 16:17:34 -06:00
02ae78de7b totally remove code to make terrain bob up and down 2023-05-16 07:50:26 -06:00
64f3dbd138 play with sizing, and stop bobbing and rotation 2023-05-16 07:49:44 -06:00
f603882d42 modify some things to remove warnings on server and in browser console 2023-05-16 06:39:36 -06:00
a9844e6e78 add beginning of visualization for statistics, unmodified from blog code 2023-05-16 06:22:19 -06:00
e4f3f9b2e0 remove extra logging 2023-05-15 16:45:22 -06:00
d7d53a5b8c refactor & shorten the 'copied' message display logic 2023-05-14 20:38:27 -06:00
44ed39b5c1 eliminate extra code for quick-message-display 2023-05-12 21:40:10 -06:00
Matthew Aaron Raymer
0dbc018c8d A bit of infinity and hack for copy message 2023-05-13 11:19:53 +08:00
fb7d51ac4c Merge pull request 'add help blurb for when we don't see info about someone that we should see' (#16) from help-visibility into master
Reviewed-on: trent_larson/kick-starter-for-time-pwa#16
2023-05-11 21:21:44 -04:00
85031f84c0 add help blurb for when we don't see info about someone that we should see 2023-05-07 19:52:40 -06:00
28 changed files with 1214 additions and 49 deletions

View File

@@ -81,6 +81,8 @@ See https://tea.xyz
## Other ## Other
### Reference Material
``` ```
// reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83 // reference material from https://github.com/trentlarson/endorser-mobile/blob/8dc8e0353e0cc80ffa7ed89ded15c8b0da92726b/src/utility/idUtility.ts#L83
@@ -183,3 +185,9 @@ export const createAndStoreIdentifier = async (mnemonicPassword) => {
return importAndStoreIdentifier(mnemonic, mnemonicPassword, false, []) return importAndStoreIdentifier(mnemonic, mnemonicPassword, false, [])
} }
``` ```
## Kudos
* [Máximo Fernández](https://medium.com/@maxfarenas) for the 3D [code](https://github.com/maxfer03/vue-three-ns) and [explanatory post](https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80)
* [Many libraries]() such as Veramo.io, Vuejs.org, threejs
* [Bush 3D model](https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439)

56
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3", "@fortawesome/vue-fontawesome": "^3.0.3",
"@pvermeer/dexie-encrypted-addon": "^2.0.5", "@pvermeer/dexie-encrypted-addon": "^2.0.5",
"@tweenjs/tween.js": "^20.0.3",
"@veramo/core": "^5.1.2", "@veramo/core": "^5.1.2",
"@veramo/credential-w3c": "^5.1.4", "@veramo/credential-w3c": "^5.1.4",
"@veramo/data-store": "^5.1.2", "@veramo/data-store": "^5.1.2",
@@ -44,6 +45,7 @@
"readable-stream": "^4.3.0", "readable-stream": "^4.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"three": "^0.152.2",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
@@ -54,6 +56,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/ramda": "^0.28.23", "@types/ramda": "^0.28.23",
"@types/three": "^0.152.0",
"@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0", "@typescript-eslint/parser": "^5.57.0",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",
@@ -7741,6 +7744,11 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/@tweenjs/tween.js": {
"version": "20.0.3",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-20.0.3.tgz",
"integrity": "sha512-SYUe1UgY5HM05EB4+0B4arq2IPjvyzKXoklXKxSYrc2IFxGm1cBrqg5XbiB5uwbs0xY5j+rj986NAJMM0KZaUw=="
},
"node_modules/@types/bn.js": { "node_modules/@types/bn.js": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz",
@@ -8006,6 +8014,31 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"node_modules/@types/stats.js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz",
"integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==",
"dev": true
},
"node_modules/@types/three": {
"version": "0.152.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.152.0.tgz",
"integrity": "sha512-9QdaV5bfZEqeQi0xkXLdnoJt7lgYZbppdBAgJSWRicdtZoCYJ34nS2QkdeuzXt+UXExofk4OWqMzdX71HeDOVg==",
"dev": true,
"dependencies": {
"@tweenjs/tween.js": "~18.6.4",
"@types/stats.js": "*",
"@types/webxr": "*",
"fflate": "~0.6.9",
"lil-gui": "~0.17.0"
}
},
"node_modules/@types/three/node_modules/@tweenjs/tween.js": {
"version": "18.6.4",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz",
"integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==",
"dev": true
},
"node_modules/@types/trusted-types": { "node_modules/@types/trusted-types": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.2.tgz",
@@ -8023,6 +8056,12 @@
"integrity": "sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==", "integrity": "sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==",
"dev": true "dev": true
}, },
"node_modules/@types/webxr": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz",
"integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==",
"dev": true
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.3.tgz",
@@ -14789,6 +14828,12 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"node_modules/fflate": {
"version": "0.6.10",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
"dev": true
},
"node_modules/figures": { "node_modules/figures": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz", "resolved": "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz",
@@ -18304,6 +18349,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/lil-gui": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz",
"integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==",
"dev": true
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.0.6.tgz", "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.0.6.tgz",
@@ -25184,6 +25235,11 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/three": {
"version": "0.152.2",
"resolved": "https://registry.npmjs.org/three/-/three-0.152.2.tgz",
"integrity": "sha512-Ff9zIpSfkkqcBcpdiFo2f35vA9ZucO+N8TNacJOqaEE6DrB0eufItVMib8bK8Pcju/ZNT6a7blE1GhTpkdsILw=="
},
"node_modules/throat": { "node_modules/throat": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",

View File

@@ -13,6 +13,7 @@
"@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0",
"@fortawesome/vue-fontawesome": "^3.0.3", "@fortawesome/vue-fontawesome": "^3.0.3",
"@pvermeer/dexie-encrypted-addon": "^2.0.5", "@pvermeer/dexie-encrypted-addon": "^2.0.5",
"@tweenjs/tween.js": "^20.0.3",
"@veramo/core": "^5.1.2", "@veramo/core": "^5.1.2",
"@veramo/credential-w3c": "^5.1.4", "@veramo/credential-w3c": "^5.1.4",
"@veramo/data-store": "^5.1.2", "@veramo/data-store": "^5.1.2",
@@ -44,6 +45,7 @@
"readable-stream": "^4.3.0", "readable-stream": "^4.3.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"register-service-worker": "^1.7.2", "register-service-worker": "^1.7.2",
"three": "^0.152.2",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-axios": "^3.5.2", "vue-axios": "^3.5.2",
"vue-class-component": "^8.0.0-0", "vue-class-component": "^8.0.0-0",
@@ -54,6 +56,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/ramda": "^0.28.23", "@types/ramda": "^0.28.23",
"@types/three": "^0.152.0",
"@typescript-eslint/eslint-plugin": "^5.57.0", "@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0", "@typescript-eslint/parser": "^5.57.0",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",

View File

@@ -5,9 +5,11 @@
- replace user-affecting console.logs with error messages (eg. catches) - replace user-affecting console.logs with error messages (eg. catches)
- stats v1 :
- 01 show numeric stats
- 01 link to world for specific stats
- contacts v1 : - contacts v1 :
- .2 warn about amounts when you cannot see them
- .1 add confirmation message when 'copy' succeeds
- .5 switch to prod server - .5 switch to prod server
- .5 Add page to show seed. - .5 Add page to show seed.
- 01 Provide a way to import the non-sensitive data. - 01 Provide a way to import the non-sensitive data.
@@ -33,7 +35,16 @@
- backup all data - backup all data
- Next Viable Product afterward - .5 customize favicon
- .5 make advanced features harder to access
- Release Minimum Viable Product :
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).
- Stats :
- 01 point out user's location on the world
- 01 present a credential selected from the stats
- 04 show gives spreading to other places
- Connect with phone contacts - Connect with phone contacts
@@ -42,3 +53,6 @@
- Peer DID - Peer DID
- DIDComm - DIDComm
- v video for multiple identities https://youtu.be/p8L87AeD76w
- v video for adding time to contacts https://youtu.be/7Yylczevp10

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@@ -0,0 +1,11 @@
Model Information:
* title: Lupine Plant
* source: https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439
* author: rufusrockwell (https://sketchfab.com/rufusrockwell)
Model License:
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
* requirements: Author must be credited. Commercial use is allowed.
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
This work is based on "Lupine Plant" (https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439) by rufusrockwell (https://sketchfab.com/rufusrockwell) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)

Binary file not shown.

View File

@@ -0,0 +1,229 @@
{
"accessors": [
{
"bufferView": 2,
"componentType": 5126,
"count": 2759,
"max": [
41.3074951171875,
40.37548828125,
87.85917663574219
],
"min": [
-35.245540618896484,
-36.895416259765625,
-0.9094290137290955
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 33108,
"componentType": 5126,
"count": 2759,
"max": [
0.9999382495880127,
0.9986748695373535,
0.9985831379890442
],
"min": [
-0.9998949766159058,
-0.9975876212120056,
-0.411094069480896
],
"type": "VEC3"
},
{
"bufferView": 3,
"componentType": 5126,
"count": 2759,
"max": [
0.9987699389457703,
0.9998998045921326,
0.9577858448028564,
1.0
],
"min": [
-0.9987726807594299,
-0.9990445971488953,
-0.999801516532898,
1.0
],
"type": "VEC4"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 2759,
"max": [
1.0061479806900024,
0.9993550181388855
],
"min": [
0.00279300007969141,
0.0011620000004768372
],
"type": "VEC2"
},
{
"bufferView": 0,
"componentType": 5125,
"count": 6378,
"type": "SCALAR"
}
],
"asset": {
"extras": {
"author": "rufusrockwell (https://sketchfab.com/rufusrockwell)",
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
"source": "https://sketchfab.com/3d-models/lupine-plant-bf30f1110c174d4baedda0ed63778439",
"title": "Lupine Plant"
},
"generator": "Sketchfab-12.68.0",
"version": "2.0"
},
"bufferViews": [
{
"buffer": 0,
"byteLength": 25512,
"name": "floatBufferViews",
"target": 34963
},
{
"buffer": 0,
"byteLength": 22072,
"byteOffset": 25512,
"byteStride": 8,
"name": "floatBufferViews",
"target": 34962
},
{
"buffer": 0,
"byteLength": 66216,
"byteOffset": 47584,
"byteStride": 12,
"name": "floatBufferViews",
"target": 34962
},
{
"buffer": 0,
"byteLength": 44144,
"byteOffset": 113800,
"byteStride": 16,
"name": "floatBufferViews",
"target": 34962
}
],
"buffers": [
{
"byteLength": 157944,
"uri": "scene.bin"
}
],
"images": [
{
"uri": "textures/lambert2SG_baseColor.png"
},
{
"uri": "textures/lambert2SG_normal.png"
}
],
"materials": [
{
"alphaCutoff": 0.2,
"alphaMode": "MASK",
"doubleSided": true,
"name": "lambert2SG",
"normalTexture": {
"index": 1
},
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"name": "Object_0",
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 0,
"TANGENT": 2,
"TEXCOORD_0": 3
},
"indices": 4,
"material": 0,
"mode": 4
}
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
2.220446049250313e-16,
-1.0,
0.0,
0.0,
1.0,
2.220446049250313e-16,
0.0,
0.0,
0.0,
0.0,
1.0
],
"name": "Sketchfab_model"
},
{
"children": [
2
],
"name": "LupineSF.obj.cleaner.materialmerger.gles"
},
{
"mesh": 0,
"name": "Object_2"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9987,
"wrapS": 10497,
"wrapT": 10497
}
],
"scene": 0,
"scenes": [
{
"name": "Sketchfab_Scene",
"nodes": [
0
]
}
],
"textures": [
{
"sampler": 0,
"source": 0
},
{
"sampler": 0,
"source": 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 MiB

View File

@@ -0,0 +1,42 @@
<template>
<div ref="scrollContainer">
<slot />
<div ref="sentinel" style="height: 1px"></div>
</div>
</template>
<script lang="ts">
import { Component, Emit, Prop, Vue } from "vue-facing-decorator";
@Component
export default class InfiniteScroll extends Vue {
@Prop({ default: 100 })
readonly distance!: number;
private observer!: IntersectionObserver;
mounted() {
const options = {
root: this.$refs.scrollContainer as HTMLElement,
rootMargin: `0px 0px ${this.distance}px 0px`,
threshold: 1.0,
};
this.observer = new IntersectionObserver(this.handleIntersection, options);
this.observer.observe(this.$refs.sentinel as HTMLElement);
}
beforeUnmount() {
this.observer.disconnect();
}
@Emit("reached-bottom")
handleIntersection(entries: IntersectionObserverEntry[]) {
const entry = entries[0];
if (entry.isIntersecting) {
return true;
}
return false;
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

View File

@@ -0,0 +1,104 @@
// from https://medium.com/nicasource/building-an-interactive-web-portfolio-with-vue-three-js-part-three-implementing-three-js-452cb375ef80
import * as TWEEN from "@tweenjs/tween.js";
import * as THREE from "three";
import { createCamera } from "./components/camera.js";
import { createLights } from "./components/lights.js";
import { createScene } from "./components/scene.js";
import { loadLandmarks } from "./components/objects/landmarks.js";
import { createTerrain } from "./components/objects/terrain.js";
import { Loop } from "./systems/Loop.js";
import { Resizer } from "./systems/Resizer.js";
import { createControls } from "./systems/controls.js";
import { createRenderer } from "./systems/renderer.js";
const COLOR1 = "#dddddd";
const COLOR2 = "#0055aa";
class World {
constructor(container, vue) {
this.PLATFORM_BORDER = 5;
this.PLATFORM_EDGE_FOR_UNKNOWNS = 10;
this.PLATFORM_SIZE = 100; // note that the loadLandmarks calculations may still assume 100
this.update = this.update.bind(this);
// Instances of camera, scene, and renderer
this.camera = createCamera();
this.scene = createScene(COLOR2);
this.renderer = createRenderer();
// necessary for models, says https://threejs.org/docs/index.html#examples/en/loaders/GLTFLoader
this.renderer.outputColorSpace = THREE.SRGBColorSpace;
this.light = null;
this.lights = [];
this.bushes = [];
// Initialize Loop
this.loop = new Loop(this.camera, this.scene, this.renderer);
container.append(this.renderer.domElement);
// Orbit Controls
const controls = createControls(this.camera, this.renderer.domElement);
// Light Instance, with optional light helper
const { light } = createLights(COLOR1);
// Terrain Instance
const terrain = createTerrain({
color: COLOR1,
height: this.PLATFORM_SIZE + this.PLATFORM_BORDER * 2,
width:
this.PLATFORM_SIZE +
this.PLATFORM_BORDER * 2 +
this.PLATFORM_EDGE_FOR_UNKNOWNS * 2,
});
this.loop.updatables.push(controls);
this.loop.updatables.push(light);
this.loop.updatables.push(terrain);
this.scene.add(light, terrain);
loadLandmarks(vue, this, this.scene, this.loop);
requestAnimationFrame(this.update);
// Responsive handler
const resizer = new Resizer(container, this.camera, this.renderer);
resizer.onResize = () => {
this.render();
};
}
update(time) {
TWEEN.update(time);
this.lights.forEach((light) => {
light.updateMatrixWorld();
light.target.updateMatrixWorld();
});
this.lights.forEach((bush) => {
bush.updateMatrixWorld();
});
requestAnimationFrame(this.update);
}
render() {
// draw a single frame
this.renderer.render(this.scene, this.camera);
}
// Animation handlers
start() {
this.loop.start();
}
stop() {
this.loop.stop();
}
}
export { World };

View File

@@ -0,0 +1,19 @@
import { PerspectiveCamera } from "three";
function createCamera() {
const camera = new PerspectiveCamera(
35, // fov = Field Of View
1, // aspect ratio (dummy value)
0.1, // near clipping plane
350 // far clipping plane
);
// move the camera back so we can view the scene
camera.position.set(0, 100, 200);
// eslint-disable-next-line @typescript-eslint/no-empty-function
camera.tick = () => {};
return camera;
}
export { createCamera };

View File

@@ -0,0 +1,14 @@
import { DirectionalLight, DirectionalLightHelper } from "three";
function createLights(color) {
const light = new DirectionalLight(color, 4);
const lightHelper = new DirectionalLightHelper(light, 0);
light.position.set(60, 100, 30);
// eslint-disable-next-line @typescript-eslint/no-empty-function
light.tick = () => {};
return { light, lightHelper };
}
export { createLights };

View File

@@ -0,0 +1,225 @@
import axios from "axios";
import * as R from "ramda";
import * as THREE from "three";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader";
import * as SkeletonUtils from "three/addons/utils/SkeletonUtils";
import * as TWEEN from "@tweenjs/tween.js";
import { AppString } from "@/constants/app";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
const ANIMATION_DURATION_SECS = 10;
const BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
const ENDORSER_ENTITY_PREFIX = "https://endorser.ch/entity/";
export async function loadLandmarks(vue, world, scene, loop) {
const endorserApiServer = AppString.DEFAULT_ENDORSER_API_SERVER;
try {
await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
const activeDid = settings?.activeDid || "";
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const account = R.find((acc) => acc.did === activeDid, accounts);
const identity = JSON.parse(account?.identity || "undefined");
const token = await accessToken(identity);
const url =
endorserApiServer + "/api/v2/report/claims?claimType=GiveAction";
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
const resp = await axios.get(url, { headers: headers });
if (resp.status === 200) {
const minDate = resp.data.data[resp.data.data.length - 1].issuedAt;
const maxDate = resp.data.data[0].issuedAt;
const minTimeMillis = new Date(minDate).getTime();
const fullTimeMillis = new Date(maxDate).getTime() - minTimeMillis;
// ratio of animation time to real time
const fakeRealRatio = (ANIMATION_DURATION_SECS * 1000) / fullTimeMillis;
// load plant model first because it takes a second
const loader = new GLTFLoader();
// choose the right plant
const modelLoc = "/models/lupine_plant/scene.gltf", // push with pokies
modScale = 0.1;
//const modelLoc = "/models/round_bush/scene.gltf", // green & pink
// modScale = 1;
//const modelLoc = "/models/coreopsis-flower.glb", // 3 flowers
// modScale = 2;
//const modelLoc = "/models/a_bush/scene.gltf", // purple leaves
// modScale = 15;
// calculate positions for each claim, especially because some are random
const locations = resp.data.data.map((claim) =>
locForGive(claim, world.PLATFORM_SIZE, world.PLATFORM_EDGE_FOR_UNKNOWNS)
);
// eslint-disable-next-line @typescript-eslint/no-this-alias
loader.load(
modelLoc,
function (gltf) {
gltf.scene.scale.set(0, 0, 0);
for (let i = 0; i < resp.data.data.length; i++) {
// claim is a GiveServerRecord (see endorserServer.ts)
const claim = resp.data.data[i];
const newPlant = SkeletonUtils.clone(gltf.scene);
const loc = locations[i];
newPlant.position.set(loc.x, 0, loc.z);
world.scene.add(newPlant);
const timeDelayMillis =
fakeRealRatio *
(new Date(claim.issuedAt).getTime() - minTimeMillis);
new TWEEN.Tween(newPlant.scale)
.delay(timeDelayMillis)
.to({ x: modScale, y: modScale, z: modScale }, 5000)
.start();
world.bushes = [...world.bushes, newPlant];
}
},
undefined,
function (error) {
console.error(error);
}
);
// calculate when lights shine on appearing claim area
for (let i = 0; i < resp.data.data.length; i++) {
// claim is a GiveServerRecord (see endorserServer.ts)
const claim = resp.data.data[i];
const loc = locations[i];
const light = createLight();
light.position.set(loc.x, 20, loc.z);
light.target.position.set(loc.x, 0, loc.z);
loop.updatables.push(light);
scene.add(light);
scene.add(light.target);
// now figure out the timing and shine a light
const timeDelayMillis =
fakeRealRatio * (new Date(claim.issuedAt).getTime() - minTimeMillis);
new TWEEN.Tween(light)
.delay(timeDelayMillis)
.to({ intensity: 100 }, 10)
.chain(
new TWEEN.Tween(light.position)
.to({ y: 5 }, 5000)
.onComplete(() => {
scene.remove(light);
light.dispose();
})
)
.start();
world.lights = [...world.lights, light];
}
} else {
console.log(
"Got bad server response status & data of",
resp.status,
resp.data
);
vue.setAlert(
"Error With Server",
"There was an error retrieving your claims from the server."
);
}
} catch (error) {
console.log("Got exception contacting server:", error);
vue.setAlert(
"Error With Server",
"There was a problem retrieving your claims from the server."
);
}
}
/**
*
* @param giveClaim
* @returns {x:float, z:float} where -50 <= x & z < 50
*/
function locForGive(giveClaim, platformWidth, borderWidth) {
let loc;
if (giveClaim?.claim?.recipient?.identifier) {
// this is directly to a person
loc = locForEthrDid(giveClaim.claim.recipient.identifier);
loc = { x: loc.x - platformWidth / 2, z: loc.z - platformWidth / 2 };
} else if (giveClaim?.object?.isPartOf?.identifier) {
// this is probably to a project
const objId = giveClaim.object.isPartOf.identifier;
if (objId.startsWith(ENDORSER_ENTITY_PREFIX)) {
loc = locForUlid(objId.substring(ENDORSER_ENTITY_PREFIX.length));
loc = { x: loc.x - platformWidth / 2, z: loc.z - platformWidth / 2 };
}
}
if (!loc) {
// it must be outside our known addresses so let's put it somewhere random
const leftSide = Math.random() < 0.5;
loc = {
x: leftSide
? -platformWidth / 2 - borderWidth / 2
: platformWidth / 2 + borderWidth / 2,
z: Math.random() * platformWidth - platformWidth / 2,
};
}
return loc;
}
/**
* Generate a deterministic x & z location based on the randomness in a ULID.
*
* We'll use the first 20 bits for the x coordinate and next 20 for the z.
* That's a bit over a trillion locations... should be enough for pretty
* unique locations for this fun kind of application within a network
* -- though they're not guaranteed unique without the full ID.
* (We're leaving 40 bits for other properties.)
*
* @param ulid
* @returns {x: float, z: float} where 0 <= x & z < 100
*/
function locForUlid(ulid) {
// The random parts of a ULID come after the first 10 characters.
const randomness = ulid.substring(10);
// That leaves 16 characters of randomness, or 80 bits.
// We're currently only using 32 possible x and z values
// because the display is pretty low-fidelity at this point.
// Similar code is below.
const x = (100 * BASE32.indexOf(randomness.substring(0, 1))) / 32;
const z = (100 * BASE32.indexOf(randomness.substring(4, 5))) / 32;
return { x, z };
}
/**
* See locForUlid
* @param did
* @returns {x: float, z: float} where 0 <= x & z < 100
*/
function locForEthrDid(did) {
// "did:ethr:0x..."
if (did.length < 51) {
return { x: 0, z: 0 };
} else {
const randomness = did.substring(11);
// We'll take the first 4 bits for 16 possible x & z values.
const xOff = parseInt(Number("0x" + randomness.substring(0, 1)), 10);
const x = (xOff * 100) / 16;
// ... and since we're reserving 20 bits total for x, start with character 5.
const zOff = parseInt(Number("0x" + randomness.substring(5, 6)), 10);
const z = (zOff * 100) / 16;
return { x, z };
}
}
function createLight() {
const light = new THREE.SpotLight(0xffffff, 0, 0, Math.PI / 8, 0.5, 0);
// eslint-disable-next-line @typescript-eslint/no-empty-function
light.tick = () => {};
return light;
}

View File

@@ -0,0 +1,29 @@
import { PlaneGeometry, MeshLambertMaterial, Mesh, TextureLoader } from "three";
export function createTerrain(props) {
const loader = new TextureLoader();
const height = loader.load("img/textures/forest-floor.png");
// w h
const geometry = new PlaneGeometry(props.width, props.height, 64, 64);
const material = new MeshLambertMaterial({
color: props.color,
flatShading: true,
map: height,
//displacementMap: height,
//displacementScale: 5,
});
const plane = new Mesh(geometry, material);
plane.position.set(0, 0, 0);
plane.rotation.x -= Math.PI * 0.5;
//Storing our original vertices position on a new attribute
plane.geometry.attributes.position.originalPosition =
plane.geometry.attributes.position.array;
// eslint-disable-next-line @typescript-eslint/no-empty-function
plane.tick = () => {};
return plane;
}

View File

@@ -0,0 +1,11 @@
import { Color, Scene } from "three";
function createScene(color) {
const scene = new Scene();
scene.background = new Color(color);
//scene.fog = new Fog(color, 60, 90);
return scene;
}
export { createScene };

View File

@@ -0,0 +1,33 @@
import { Clock } from "three";
const clock = new Clock();
class Loop {
constructor(camera, scene, renderer) {
this.camera = camera;
this.scene = scene;
this.renderer = renderer;
this.updatables = [];
}
start() {
this.renderer.setAnimationLoop(() => {
this.tick();
// render a frame
this.renderer.render(this.scene, this.camera);
});
}
stop() {
this.renderer.setAnimationLoop(null);
}
tick() {
const delta = clock.getDelta();
for (const object of this.updatables) {
object.tick(delta);
}
}
}
export { Loop };

View File

@@ -0,0 +1,33 @@
const setSize = (container, camera, renderer) => {
// These are great for full-screen, which adjusts to a window.
const height = window.innerHeight;
const width = window.innerWidth - 50;
// These are better for fitting in a container, which stays that size.
//const height = container.scrollHeight;
//const width = container.scrollWidth;
camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio);
};
class Resizer {
constructor(container, camera, renderer) {
// set initial size on load
setSize(container, camera, renderer);
window.addEventListener("resize", () => {
// set the size again if a resize occurs
setSize(container, camera, renderer);
// perform any custom actions
this.onResize();
});
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
onResize() {}
}
export { Resizer };

View File

@@ -0,0 +1,38 @@
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { MathUtils } from "three";
function createControls(camera, canvas) {
const controls = new OrbitControls(camera, canvas);
//enable controls?
controls.enabled = true;
controls.autoRotate = false;
//controls.autoRotateSpeed = 0.2;
// control limits
// It's recommended to set some control boundaries,
// to prevent the user from clipping with the objects.
// y axis
controls.minPolarAngle = MathUtils.degToRad(40); // default
controls.maxPolarAngle = MathUtils.degToRad(75);
// x axis
// controls.minAzimuthAngle = ...
// controls.maxAzimuthAngle = ...
//smooth camera:
// remember to add to loop updatables to work
controls.enableDamping = true;
//controls.enableZoom = false;
controls.maxDistance = 250;
//controls.enablePan = false;
controls.tick = () => controls.update();
return controls;
}
export { createControls };

View File

@@ -0,0 +1,13 @@
import { WebGLRenderer } from "three";
function createRenderer() {
const renderer = new WebGLRenderer({ antialias: true });
// turn on the physically correct lighting model
// (The browser complains: "THREE.WebGLRenderer: .physicallyCorrectLights has been removed. Set enderer.useLegacyLights instead." However, that changes the lighting in a way that doesn't look better.)
renderer.physicallyCorrectLights = true;
return renderer;
}
export { createRenderer };

View File

@@ -121,6 +121,14 @@ const routes: Array<RouteRecordRaw> = [
component: () => component: () =>
import(/* webpackChunkName: "start" */ "../views/StartView.vue"), import(/* webpackChunkName: "start" */ "../views/StartView.vue"),
}, },
{
path: "/statistics",
name: "statistics",
component: () =>
import(
/* webpackChunkName: "statistics" */ "../views/StatisticsView.vue"
),
},
]; ];
/** @type {*} */ /** @type {*} */

View File

@@ -89,9 +89,15 @@
<div class="text-slate-500 text-sm font-bold">ID</div> <div class="text-slate-500 text-sm font-bold">ID</div>
<div class="text-sm text-slate-500 flex justify-start items-center mb-1"> <div class="text-sm text-slate-500 flex justify-start items-center mb-1">
<code class="truncate">{{ activeDid }}</code> <code class="truncate">{{ activeDid }}</code>
<button @click="copy(activeDid)" class="ml-2"> <button
@click="
doCopyTwoSecRedo(activeDid, () => (showDidCopy = !showDidCopy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa> <fa icon="copy" class="text-slate-400 fa-fw"></fa>
</button> </button>
<span v-show="showDidCopy">Copied!</span>
<span class="whitespace-nowrap ml-4"> <span class="whitespace-nowrap ml-4">
<button <button
class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md" class="text-xs uppercase bg-slate-500 text-white px-1.5 py-1 rounded-md"
@@ -109,25 +115,43 @@
<div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div> <div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
<div class="text-sm text-slate-500 flex justify-start items-center mb-1"> <div class="text-sm text-slate-500 flex justify-start items-center mb-1">
<code class="truncate">{{ publicBase64 }}</code> <code class="truncate">{{ publicBase64 }}</code>
<button @click="copy(publicBase64)" class="ml-2"> <button
@click="
doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa> <fa icon="copy" class="text-slate-400 fa-fw"></fa>
</button> </button>
<span v-show="showB64Copy">Copied!</span>
</div> </div>
<div class="text-slate-500 text-sm font-bold">Public Key (hex)</div> <div class="text-slate-500 text-sm font-bold">Public Key (hex)</div>
<div class="text-sm text-slate-500 flex justify-start items-center mb-1"> <div class="text-sm text-slate-500 flex justify-start items-center mb-1">
<code class="truncate">{{ publicHex }}</code> <code class="truncate">{{ publicHex }}</code>
<button @click="copy(publicHex)" class="ml-2"> <button
@click="
doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa> <fa icon="copy" class="text-slate-400 fa-fw"></fa>
</button> </button>
<span v-show="showPubCopy">Copied!</span>
</div> </div>
<div class="text-slate-500 text-sm font-bold">Derivation Path</div> <div class="text-slate-500 text-sm font-bold">Derivation Path</div>
<div class="text-sm text-slate-500 flex justify-start items-center mb-1"> <div class="text-sm text-slate-500 flex justify-start items-center mb-1">
<code class="truncate">{{ derivationPath }}</code> <code class="truncate">{{ derivationPath }}</code>
<button @click="copy(derivationPath)" class="ml-2"> <button
@click="
doCopyTwoSecRedo(derivationPath, () => (showDerCopy = !showDerCopy))
"
class="ml-2"
>
<fa icon="copy" class="text-slate-400 fa-fw"></fa> <fa icon="copy" class="text-slate-400 fa-fw"></fa>
</button> </button>
<span v-show="showDerCopy">Copied!</span>
</div> </div>
</div> </div>
@@ -206,7 +230,7 @@
<div class="flex py-2"> <div class="flex py-2">
<button class="text-center text-md text-blue-500" @click="checkLimits()"> <button class="text-center text-md text-blue-500" @click="checkLimits()">
Check Registration and Claim Limits Check Limits
</button> </button>
<div v-if="!!limits?.nextWeekBeginDateTime" class="px-9"> <div v-if="!!limits?.nextWeekBeginDateTime" class="px-9">
<span class="font-bold">Rate Limits</span> <span class="font-bold">Rate Limits</span>
@@ -233,6 +257,17 @@
</span> </span>
</div> </div>
<div>
<button class="text-blue-500 px-2">
<router-link
:to="{ name: 'statistics' }"
class="block text-center py-3 px-1"
>
See Your Statistics
</router-link>
</button>
</div>
<div v-bind:class="computedAlertClassNames()"> <div v-bind:class="computedAlertClassNames()">
<button <button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2" class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@@ -288,7 +323,18 @@ export default class AccountViewView extends Vue {
limits: RateLimits | null = null; limits: RateLimits | null = null;
showContactGives = false; showContactGives = false;
copy = useClipboard().copy; showDidCopy = false;
showDerCopy = false;
showB64Copy = false;
showPubCopy = false;
// call fn, copy text to the clipboard, then redo fn after 2 seconds
doCopyTwoSecRedo(text, fn) {
fn();
useClipboard()
.copy(text)
.then(() => setTimeout(fn, 2000));
}
handleChange() { handleChange() {
this.showContactGives = !this.showContactGives; this.showContactGives = !this.showContactGives;
@@ -306,7 +352,6 @@ export default class AccountViewView extends Vue {
// assign this to a class variable, eg. "registerThisUser = testServerRegisterUser", // assign this to a class variable, eg. "registerThisUser = testServerRegisterUser",
// select a component in the extension, and enter in the console: $vm.ctx.registerThisUser() // select a component in the extension, and enter in the console: $vm.ctx.registerThisUser()
//testServerRegisterUser(); //testServerRegisterUser();
try { try {
await db.open(); await db.open();
const settings = await db.settings.get(MASTER_SETTINGS_KEY); const settings = await db.settings.get(MASTER_SETTINGS_KEY);

View File

@@ -72,7 +72,7 @@
<fa icon="circle-check" class="text-green-600 fa-fw ml-1" /> <fa icon="circle-check" class="text-green-600 fa-fw ml-1" />
<span class="tooltiptext">Confirmed</span> <span class="tooltiptext">Confirmed</span>
</span> </span>
<button v-else class="tooltip" @click="confirmGiven(record)"> <button v-else class="tooltip" @click="confirm(record)">
<fa icon="circle" class="text-blue-600 fa-fw ml-1" /> <fa icon="circle" class="text-blue-600 fa-fw ml-1" />
<span class="tooltiptext">Unconfirmed</span> <span class="tooltiptext">Unconfirmed</span>
</button> </button>
@@ -239,11 +239,7 @@ export default class ContactsView extends Vue {
} }
} }
async confirmGiven(record: GiveServerRecord) { async confirm(record: GiveServerRecord) {
if (!confirm("Are you sure you want to mark this as confirmed?")) {
return;
}
// Make claim // Make claim
// I use clone here because otherwise it gets a Proxy object. // I use clone here because otherwise it gets a Proxy object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -49,6 +49,18 @@
Your Contacts Your Contacts
</h1> </h1>
<div class="flex justify-between py-2">
<span />
<span>
<router-link
:to="{ name: 'help' }"
class="text-xs uppercase bg-blue-500 text-white px-1.5 py-1 rounded-md ml-1"
>
Help
</router-link>
</span>
</div>
<!-- New Contact --> <!-- New Contact -->
<div class="mb-4 flex"> <div class="mb-4 flex">
<input <input
@@ -165,12 +177,11 @@
: (givenByMeUnconfirmed[contact.did] || 0) : (givenByMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<span class="tooltiptext-left"> <span
{{ v-if="givenByMeDescriptions[contact.did]"
givenByMeDescriptions[contact.did] class="tooltiptext-left"
? "Most recently: " + givenByMeDescriptions[contact.did] >
: "(None given yet.)" {{ givenByMeDescriptions[contact.did] }}
}}
</span> </span>
<button <button
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@@ -191,12 +202,11 @@
: (givenToMeUnconfirmed[contact.did] || 0) : (givenToMeUnconfirmed[contact.did] || 0)
/* eslint-enable prettier/prettier */ /* eslint-enable prettier/prettier */
}} }}
<span class="tooltiptext-left"> <span
{{ v-if="givenToMeDescriptions[contact.did]"
givenToMeDescriptions[contact.did] class="tooltiptext-left"
? "Most recently: " + givenToMeDescriptions[contact.did] >
: "(None received yet.)" {{ givenToMeDescriptions[contact.did] }}
}}
</span> </span>
<button <button
class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6" class="text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mb-6"
@@ -332,8 +342,8 @@ export default class ContactsView extends Vue {
const contactUnconfirmed: Record<string, number> = {}; const contactUnconfirmed: Record<string, number> = {};
const allData: Array<GiveServerRecord> = resp.data.data; const allData: Array<GiveServerRecord> = resp.data.data;
for (const give of allData) { for (const give of allData) {
const recipDid: string = give.recipientDid; if (give.unit == "HUR") {
if (recipDid && give.unit == "HUR") { const recipDid: string = give.recipientDid;
if (give.amountConfirmed) { if (give.amountConfirmed) {
const prevAmount = contactConfirmed[recipDid] || 0; const prevAmount = contactConfirmed[recipDid] || 0;
contactConfirmed[recipDid] = prevAmount + give.amount; contactConfirmed[recipDid] = prevAmount + give.amount;
@@ -347,6 +357,7 @@ export default class ContactsView extends Vue {
} }
} }
} }
//console.log("Done retrieving gives", contactConfirmed);
this.givenByMeDescriptions = contactDescriptions; this.givenByMeDescriptions = contactDescriptions;
this.givenByMeConfirmed = contactConfirmed; this.givenByMeConfirmed = contactConfirmed;
this.givenByMeUnconfirmed = contactUnconfirmed; this.givenByMeUnconfirmed = contactUnconfirmed;

View File

@@ -48,20 +48,20 @@
</nav> </nav>
<!-- CONTENT --> <!-- CONTENT -->
<section id="Content" class="p-4 pb-24"> <section id="Content" class="p-6 pb-24">
<!-- Heading --> <!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8"> <h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Help Help
</h1> </h1>
<div> <div>
<h2 class="text-xl font-semibold py-4">Introduction</h2> <h2 class="text-xl font-semibold">Introduction</h2>
<p> <p>
This app is a window into data that you and your friends own, focused on This app is a window into data that you and your friends own, focused on
gifts and collaboration. gifts and collaboration.
</p> </p>
<h2 class="text-xl font-semibold py-4">How do I backup all my data?</h2> <h2 class="text-xl font-semibold">How do I backup all my data?</h2>
<p> <p>
There are two parts to backup your data: the identifier secrets and the There are two parts to backup your data: the identifier secrets and the
other data such as settings, contacts, etc. other data such as settings, contacts, etc.
@@ -73,7 +73,7 @@
</h2> </h2>
<ul class="list-disc list-inside"> <ul class="list-disc list-inside">
<li> <li>
Go to your Identity <fa icon="circle-user" class="fa-fw" /> page. Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
</li> </li>
<li> <li>
Click on "Backup Identifier Seed" and follow the instructions. Click on "Backup Identifier Seed" and follow the instructions.
@@ -85,7 +85,7 @@
</h2> </h2>
<ul class="list-disc list-inside"> <ul class="list-disc list-inside">
<li> <li>
Go to your Identity <fa icon="circle-user" class="fa-fw" /> page. Go to Your Identity <fa icon="circle-user" class="fa-fw" /> page.
</li> </li>
<li> <li>
Click on "Download Settings...". That will save a file to your Click on "Download Settings...". That will save a file to your
@@ -95,7 +95,7 @@
</ul> </ul>
</div> </div>
<h2 class="text-xl font-semibold py-4">How do I restore my data?</h2> <h2 class="text-xl font-semibold">How do I restore my data?</h2>
<p> <p>
There are two parts to restore your data: the identity secrets and the There are two parts to restore your data: the identity secrets and the
other data such as settings, contacts, etc. other data such as settings, contacts, etc.
@@ -135,24 +135,30 @@
</ul> </ul>
</div> </div>
<h2 class="text-xl font-semibold py-4"> <h2 class="text-xl font-semibold">
How do I get registered to make claims?
</h2>
<p>
Contact a current user. They will be able to register you on their
Contacts <fa icon="users" class="fa-fw" /> screen. Note that they have a
limited number of registrations.
</p>
<h2 class="text-xl font-semibold py-4">
How do I add someone to my contacts? How do I add someone to my contacts?
</h2> </h2>
<p> <p>
Tell them to copy their ID, which typically starts with "did:ethr:...", Tell them to copy their ID, which typically starts with "did:ethr:...",
and send it to you. Go to the Contacts and send it to you. Go to the Contacts
<fa icon="users" class="fa-fw" /> page and enter that into the top form. <fa icon="circle-user" class="fa-fw" /> page and enter that into the top
You may add a name by adding a comma followed by their name; you may form. You may add a name by adding a comma followed by their name; you
also add their public key by adding another comma followed by the key. may also add their public key by adding another comma followed by the
key.
</p>
<h2 class="text-xl font-semibold">
I know there is a record from someone, so why can't I see that info?
</h2>
<p>
Sometimes someone else has made claims -- meaning they have given time,
or recognized time given, or declared a project -- but you cannot see
anything associated with them. The reason may be because they have not
given you permission to see their information. Ask them to add you to
their contact list and make sure the eye next to your name is open like
this
<fa icon="eye" class="fa-fw" /> and not closed like this
<fa icon="eye-slash" class="fa-fw" />.
</p> </p>
</div> </div>
</section> </section>

View File

@@ -0,0 +1,212 @@
<template>
<!-- QUICK NAV -->
<nav id="QuickNav" class="fixed bottom-0 left-0 right-0 bg-slate-200 z-50">
<ul class="flex text-2xl p-2 gap-2">
<!-- Home Feed -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link :to="{ name: 'home' }" class="block text-center py-3 px-1">
<fa icon="house-chimney" class="fa-fw"></fa>
</router-link>
</li>
<!-- Search -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'discover' }"
class="block text-center py-3 px-1"
>
<fa icon="magnifying-glass" class="fa-fw"></fa>
</router-link>
</li>
<!-- Projects -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'projects' }"
class="block text-center py-3 px-1"
>
<fa icon="folder-open" class="fa-fw"></fa>
</router-link>
</li>
<!-- Contacts -->
<li class="basis-1/5 rounded-md text-slate-500">
<router-link
:to="{ name: 'contacts' }"
class="block text-center py-3 px-1"
>
<fa icon="users" class="fa-fw"></fa>
</router-link>
</li>
<!-- Profile -->
<li class="basis-1/5 rounded-md bg-slate-400 text-white">
<router-link
:to="{ name: 'account' }"
class="block text-center py-3 px-1"
>
<fa icon="circle-user" class="fa-fw"></fa>
</router-link>
</li>
</ul>
</nav>
<!-- CONTENT -->
<section id="Content" class="p-6 pb-24">
<!-- Heading -->
<h1 id="ViewHeading" class="text-4xl text-center font-light pt-4 mb-8">
Your Statistics
</h1>
<button class="float-right" @click="captureGraphics()">Screenshot</button>
<!-- Another place to play with the sizing is in Resizer.setSize -->
<div id="scene-container" class="h-screen"></div>
<div v-bind:class="computedAlertClassNames()">
<button
class="close-button bg-slate-200 w-8 leading-loose rounded-full absolute top-2 right-2"
@click="onClickClose()"
>
<fa icon="xmark"></fa>
</button>
<h4 class="font-bold pr-5">{{ alertTitle }}</h4>
<p>{{ alertMessage }}</p>
</div>
</section>
</template>
<script lang="ts">
import { SVGRenderer } from "three/addons/renderers/SVGRenderer.js";
import { Component, Vue } from "vue-facing-decorator";
import { World } from "@/components/World/World.js";
@Component
export default class StatisticsView extends Vue {
world: World;
mounted() {
const container = document.querySelector("#scene-container");
const newWorld = new World(container, this);
newWorld.start();
this.world = newWorld;
}
public captureGraphics() {
/**
// from https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#examples
// Adds a blank image
const dataBlob = document
.querySelector("#scene-container")
.firstChild.toBlob((blob) => {
const newImg = document.createElement("img");
const url = URL.createObjectURL(blob);
newImg.onload = () => {
// no longer need to read the blob so it's revoked
URL.revokeObjectURL(url);
};
newImg.src = url;
document.body.appendChild(newImg);
});
**/
/**
// Yields a blank page with the iframe below
const dataUrl = document
.querySelector("#scene-container")
.firstChild.toDataURL("image/png");
**/
/**
// Yields a blank page with the iframe below
const dataUrl = this.world.renderer.domElement.toDataURL("image/png");
**/
/**
// Show the image in a new tab
const iframe = `
<iframe
src="${dataUrl}"
frameborder="0"
style="border:0; top:0px; left:0px; bottom:0px; right:0px; width:100%; height:100%;"
allowfullscreen>
</iframe>`;
const win = window.open();
win.document.open();
win.document.write(iframe);
win.document.close();
**/
// from https://stackoverflow.com/a/17407392/845494
// This yields a file with funny formatting.
//const image = const dataUrl.replace("image/png", "image/octet-stream");
/**
// Yields a blank image at the bottom of the page
// from https://discourse.threejs.org/t/save-screenshot-on-server/39900/3
const img = new Image();
img.src = this.world.renderer.domElement.toDataURL();
document.body.appendChild(img);
**/
/**
* This yields an SVG that only shows white and black highlights
// from https://stackoverflow.com/questions/27632621/exporting-from-three-js-scene-to-svg-or-other-vector-format
**/
const rendererSVG = new SVGRenderer();
rendererSVG.setSize(window.innerWidth, window.innerHeight);
rendererSVG.render(this.world.scene, this.world.camera);
//document.body.appendChild(rendererSVG.domElement);
ExportToSVG(rendererSVG, "test.svg");
}
alertTitle = "";
alertMessage = "";
isAlertVisible = false;
public setAlert(title, message) {
this.alertTitle = title;
this.alertMessage = message;
this.isAlertVisible = true;
}
public onClickClose() {
this.isAlertVisible = false;
this.alertTitle = "";
this.alertMessage = "";
}
public computedAlertClassNames() {
return {
hidden: !this.isAlertVisible,
"dismissable-alert": true,
"bg-slate-100": true,
"p-5": true,
rounded: true,
"drop-shadow-lg": true,
fixed: true,
"top-3": true,
"inset-x-3": true,
"transition-transform": true,
"ease-in": true,
"duration-300": true,
};
}
}
function ExportToSVG(rendererSVG, filename) {
const XMLS = new XMLSerializer();
const svgfile = XMLS.serializeToString(rendererSVG.domElement);
const svgData = svgfile;
const preface = '<?xml version="1.0" standalone="no"?>\r\n';
const svgBlob = new Blob([preface, svgData], {
type: "image/svg+xml;charset=utf-8",
});
const svgUrl = URL.createObjectURL(svgBlob);
const downloadLink = document.createElement("a");
downloadLink.href = svgUrl;
downloadLink.download = filename;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
</script>

View File

@@ -7,4 +7,9 @@ module.exports = defineConfig({
topLevelAwait: true, topLevelAwait: true,
}, },
}, },
pwa: {
iconPaths: {
faviconSVG: 'img/icons/safari-pinned-tab.svg',
}
}
}); });