1
0
mirror of https://github.com/flutter/samples.git synced 2025-11-08 13:58:47 +00:00

Publish web_embedding (#1777)

## Pre-launch Checklist

- [x] I read the [Flutter Style Guide] _recently_, and have followed its
advice.
- [x] I signed the [CLA].
- [x] I read the [Contributors Guide].
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-devrel
channel on [Discord].

<!-- Links -->
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[CLA]: https://cla.developers.google.com/
[Discord]: https://github.com/flutter/flutter/wiki/Chat
[Contributors Guide]:
https://github.com/flutter/samples/blob/main/CONTRIBUTING.md


Co-authored-by: Mark Thompson
<2554588+MarkTechson@users.noreply.github.com>
Co-authored-by: David Iglesias <ditman@gmail.com>

Co-authored-by: Mark Thompson <2554588+MarkTechson@users.noreply.github.com>
Co-authored-by: David Iglesias <ditman@gmail.com>
This commit is contained in:
Brett Morgan
2023-05-06 10:53:17 +10:00
committed by GitHub
parent b703f1f3f9
commit 91cb685d1f
61 changed files with 14950 additions and 0 deletions

View File

@@ -0,0 +1,260 @@
@font-face {
font-family: "DM Sans";
src: url(../fonts/DMSans-Regular.ttf);
font-weight: normal;
}
@font-face {
font-family: "DM Sans";
src: url(../fonts/DMSans-Bold.ttf);
font-weight: 700;
}
/** Reset */
* {
box-sizing: border-box;
font-family: "DM Sans", sans-serif;
}
html, body {
margin: 0;
padding: 0;
min-height: 100vh;
}
body {
background-color: #fff;
background-image: radial-gradient(
ellipse at bottom,
#fafafa 5%,
transparent 60%
),
linear-gradient(136deg, transparent, #eee 290%),
linear-gradient(115deg, #fafafa, transparent 40%),
linear-gradient(180deg, transparent 0, #ddd 70%),
radial-gradient(ellipse at -70% -180%, transparent 80%, #eee 0),
radial-gradient(ellipse at bottom, #71c7ee 40%, transparent 80%),
radial-gradient(ellipse at 5% 340%, transparent 80%, #ddd 0);
background-repeat: no-repeat;
color: #555;
}
/** Layout **/
body { display: flex; flex-direction: column; }
section.contents {
flex: 1 1 auto;
flex-direction: row;
display: flex;
}
section.contents aside {
flex: 0;
display: flex;
flex-direction: column;
order: -1;
}
section.contents aside fieldset {
display: flex;
flex-flow: wrap;
justify-content: space-between;
align-items: flex-end;
}
section.contents aside .align-top {
align-self: flex-start;
}
section.contents article {
flex: 1;
margin-top: 50px;
display: flex;
justify-content: center;
}
/** Title */
h1 {
font-weight: 700;
font-size: 48px;
padding: 0;
line-height: .9em;
letter-spacing: -2px;
margin: 0 0 30px 0;
}
/** Controls for the demo (left column) */
#demo_controls {
background: linear-gradient(90deg, rgba(255,255,255,1) 10%, rgba(255,255,255,0) 100%);
padding: 40px 20px 0px 20px;
z-index: 10;
}
#demo_controls fieldset {
padding: 0;
border: none;
width: 210px;
}
#demo_controls legend {
text-align: center;
font-size: 20px;
line-height: 40px;
margin-bottom: 3px;
}
#demo_controls select.screen {
display: block;
width: 120px;
padding: 4px;
text-align: center;
margin-bottom: 10px;
}
#demo_controls input {
display: block;
width: 100px;
margin: 0 0 10px 0;
text-align: center;
}
/** Keep controls that */
#demo_controls .tight input {
margin: 0px;
}
#demo_controls input[type="button"] {
line-height: 10px;
font-size: 14px;
border-radius: 15px;
border: 1px solid #aaa;
border-style: outset;
background-color: #fff;
height: 30px;
color: #555;
transition: all 100ms ease-in-out;
cursor: pointer;
}
#demo_controls input[type="button"]:hover {
/* .active:hover background-color: #96B6E3;*/
border-color: #1c68d4;
background-color: #1c68d4;
color: white;
}
#demo_controls input[type="button"].active {
border-color: #1c68d4;
background-color: #1c68d4;
color: white;
}
#demo_controls input#value {
font-size: 32px;
line-height: 1em;
min-height: 30px;
color: #888;
}
#demo_controls input#increment {
/* Center vertically next to taller input#value */
position: relative;
top: -6px;
}
#demo_controls .disabled {
pointer-events: none;
opacity: .5;
}
/** The style for the DIV where flutter will be rendered, and the CSS fx */
#flutter_target {
border: 1px solid #aaa;
width: 320px;
height: 480px;
border-radius: 0px;
transition: all 150ms ease-in;
}
#flutter_target.resize {
width: 480px;
height: 320px;
}
#flutter_target.spin { animation: spin 6400ms ease-in-out infinite; }
#flutter_target.shadow { position: relative; }
#flutter_target.shadow::before {
content: "";
position: absolute;
display: block;
width: 100%;
top: calc(100% - 1px);
left: 0;
height: 1px;
background-color: black;
border-radius: 50%;
z-index: -1;
transform: rotateX(80deg);
box-shadow: 0px 0px 60px 38px rgb(0 0 0 / 25%);
}
#flutter_target.mirror {
-webkit-box-reflect: below 0px linear-gradient(to bottom, rgba(0,0,0,0.0), rgba(0,0,0,0.4));
}
@keyframes spin {
0% {
transform: perspective(1000px) rotateY(0deg);
animation-timing-function: ease-in;
}
15% {
transform: perspective(1000px) rotateY(165deg);
animation-timing-function: linear;
}
75% {
transform: perspective(1000px) rotateY(195deg);
animation-timing-function: linear;
}
90% {
transform: perspective(1000px) rotateY(359deg);
animation-timing-function: ease-out;
}
100% {
transform: perspective(1000px) rotateY(359deg);
animation-timing-function: linear;
}
}
/** "Handheld"/Device mode container */
#handheld::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: url(../icons/unsplash-x9WGMWwp1NM.png) no-repeat;
background-size: 1000px;
background-position: top right;
opacity: 1;
transition: opacity 200ms ease-out;
}
#handheld::after {
content: "";
position: absolute;
display: block;
width: 77px;
height: 67px;
top: 534px;
right: 573px;
background: url(../icons/nail.png) no-repeat;
background-size: 77px;
opacity: 1;
transition: opacity 200ms ease-out;
}
#handheld.hidden::before,
#handheld.hidden::after {
opacity: 0;
}
#flutter_target.handheld {
position: absolute;
right: 0px;
transform-origin: 0px 0px 0px;
transform: rotate(-14.1deg) scale(0.80) translate(-539px, -45px);
width: 316px;
height: 678px;
border-radius: 34px;
border: 1px solid #000;
overflow: hidden;
}
.imageAttribution {
position: absolute;
bottom: 6px;
right: 6px;
font-size: 10px;
}
.imageAttribution, .imageAttribution a { color: #fff; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,93 @@
Copyright 2014-2017 Indian Type Foundry (info@indiantypefoundry.com). Copyright 2019 Google LLC.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -0,0 +1,100 @@
<!DOCTYPE html>
<html>
<head>
<base href="/" />
<meta charset="UTF-8" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta name="description" content="A Flutter Web Element embedding demo." />
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Flutter Element embedding" />
<link rel="apple-touch-icon" href="icons/Icon-192.png" />
<link rel="preload" as="image" href="icons/unsplash-x9WGMWwp1NM.png" />
<!-- Favicon -->
<link rel="icon" type="image/png" href="icons/favicon.png" />
<title>Element embedding</title>
<link rel="manifest" href="manifest.json" />
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<link rel="stylesheet" type="text/css" href="css/style.css" />
</head>
<body>
<section class="contents">
<article>
<div id="flutter_target"></div>
</article>
<aside id="demo_controls">
<h1>Element embedding</h1>
<fieldset id="fx">
<legend>Effects</legend>
<input value="Shadow" data-fx="shadow" type="button" class="fx" />
<input value="Mirror 🧪" data-fx="mirror" type="button" class="fx" />
<input value="Resize" data-fx="resize" type="button" class="fx align-top" />
<div class="tight">
<input value="Spin" data-fx="spin" type="button" class="fx" />
<input type="range" value="0" min="-180" max="180" list="markers" id="rotation" class="tight" />
<datalist id="markers">
<option value="-180"></option>
<option value="-135"></option>
<option value="-45"></option>
<option value="0"></option>
<option value="45"></option>
<option value="135"></option>
<option value="180"></option>
</datalist>
</div>
<input value="Device" data-fx="handheld" type="button" class="fx" />
</fieldset>
<fieldset id="interop">
<legend>JS Interop</legend>
<label for="screen-selector">
Screen
<select name="screen-select" id="screen-selector" class="screen">
<option value="counter">Counter</option>
<option value="textField">TextField</option>
<option value="custom">Custom App</option>
</select>
</label>
<label for="value">
Value
<input id="value" value="" type="text" readonly />
</label>
<input
id="increment"
value="Increment"
type="button"
/>
</fieldset>
</aside>
</section>
<script>
window.addEventListener("load", function (ev) {
// Embed flutter into div#flutter_target
let target = document.querySelector("#flutter_target");
_flutter.loader.loadEntrypoint({
onEntrypointLoaded: async function (engineInitializer) {
let appRunner = await engineInitializer.initializeEngine({
hostElement: target,
});
await appRunner.runApp();
},
});
});
</script>
<script src="js/demo-js-interop.js" defer></script>
<script src="js/demo-css-fx.js" defer></script>
</body>
</html>

View File

@@ -0,0 +1,82 @@
// Manages toggling the VFX of the buttons
(function () {
"use strict";
let handheld;
let fxButtons = document.querySelector("#fx");
let flutterTarget = document.querySelector("#flutter_target");
let attribution = document.createElement("span");
attribution.className = "imageAttribution";
attribution.innerHTML = "Photo by <a href='https://unsplash.com/photos/x9WGMWwp1NM' rel='noopener noreferrer' target='_blank'>Nathana Rebouças</a> on Unsplash";
// (Re)moves the flutterTarget inside a div#handheld.
function handleHandHeld(fx) {
resetRotation();
if (!handheld) {
handheld = document.createElement("div");
handheld.id = "handheld";
handheld.classList.add("hidden");
// Inject before the flutterTarget
flutterTarget.parentNode.insertBefore(handheld, flutterTarget);
handheld.append(flutterTarget);
handheld.append(attribution);
window.setTimeout(function () {
handheld.classList.remove("hidden");
}, 100);
// Disable all effects on the flutter container
flutterTarget.className = "";
setOtherFxEnabled(false);
} else {
handheld.classList.add("hidden");
window.setTimeout(function () {
handheld.parentNode.insertBefore(flutterTarget, handheld);
handheld.remove();
handheld = null;
}, 210);
setOtherFxEnabled(true);
}
window.requestAnimationFrame(function () {
// Let the browser flush the DOM...
flutterTarget.classList.toggle(fx);
});
}
// Sets a rotation style on the flutterTarget (in degrees).
function handleRotation(degrees) {
flutterTarget.style.transform = `perspective(1000px) rotateY(${degrees}deg)`;
}
// Removes the inline style from the flutterTarget.
function resetRotation() {
flutterTarget.style = null;
}
// Enables/disables the buttons that are not compatible with the "handheld" mode.
function setOtherFxEnabled(enabled) {
fxButtons.querySelectorAll('input').forEach((btn) => {
if (btn.dataset.fx != 'handheld') {
btn.classList.toggle('disabled', !enabled);
}
});
}
// Handles clicks on the buttons inside #fx.
fxButtons.addEventListener("click", (event) => {
let fx = event.target.dataset.fx;
if (fx === "handheld") {
handleHandHeld(fx);
return;
}
flutterTarget.classList.toggle(fx);
});
fxButtons.addEventListener("input", (event) => {
if (event.target.id === "rotation") {
flutterTarget.classList.toggle("spin", false);
handleRotation(event.target.value);
}
})
})();

View File

@@ -0,0 +1,43 @@
// Sets up a channel to JS-interop with Flutter
(function() {
"use strict";
// This function will be called from Flutter when it prepares the JS-interop.
window._stateSet = function () {
window._stateSet = function () {
console.log("Call _stateSet only once!");
};
// The state of the flutter app, see `class _MyAppState` in lib/main.dart.
let appState = window._appState;
let valueField = document.querySelector("#value");
let updateState = function () {
valueField.value = appState.count;
};
// Register a callback to update the HTML field from Flutter.
appState.addHandler(updateState);
// Render the first value (0).
updateState();
let incrementButton = document.querySelector("#increment");
incrementButton.addEventListener("click", (event) => {
appState.increment();
});
let screenSelector = document.querySelector("#screen-selector");
screenSelector.addEventListener("change", (event) => {
appState.changeDemoScreenTo(event.target.value);
setJsInteropControlsEnabled(event.target.value === 'counter');
});
// Enables/disables the Value/Increment controls.
function setJsInteropControlsEnabled(enabled) {
let elements = document.querySelectorAll("#increment, label[for='value']");
elements.forEach((el) => {
el.classList.toggle('disabled', !enabled);
})
}
};
}());

View File

@@ -0,0 +1,35 @@
{
"name": "element_embedding_demo",
"short_name": "element_embedding",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "An example of how to embed a Flutter Web app into any HTML Element of a page.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}